mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Improve OSS <-> RSForm UI
This commit is contained in:
parent
4f9f27dfd3
commit
600b0c01ef
|
@ -5,6 +5,7 @@ from .data_access import (
|
|||
LibraryItemBaseSerializer,
|
||||
LibraryItemCloneSerializer,
|
||||
LibraryItemDetailsSerializer,
|
||||
LibraryItemReferenceSerializer,
|
||||
LibraryItemSerializer,
|
||||
UsersListSerializer,
|
||||
UserTargetSerializer,
|
||||
|
|
|
@ -17,6 +17,14 @@ class LibraryItemBaseSerializer(serializers.ModelSerializer):
|
|||
read_only_fields = ('id',)
|
||||
|
||||
|
||||
class LibraryItemReferenceSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: reference to LibraryItem. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = LibraryItem
|
||||
fields = 'id', 'alias'
|
||||
|
||||
|
||||
class LibraryItemSerializer(serializers.ModelSerializer):
|
||||
''' Serializer: LibraryItem entry limited access. '''
|
||||
class Meta:
|
||||
|
|
|
@ -84,7 +84,7 @@ class OperationUpdateSerializer(serializers.Serializer):
|
|||
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
for operation in attrs['arguments']:
|
||||
if operation.oss != oss:
|
||||
if operation.oss_id != oss.pk:
|
||||
raise serializers.ValidationError({
|
||||
'arguments': msg.operationNotInOSS(oss.title)
|
||||
})
|
||||
|
@ -110,7 +110,7 @@ class OperationUpdateSerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError({
|
||||
f'{original_cst.pk}': msg.substituteDouble(original_cst.alias)
|
||||
})
|
||||
if original_cst.schema == substitution_cst.schema:
|
||||
if original_cst.schema_id == substitution_cst.schema_id:
|
||||
raise serializers.ValidationError({
|
||||
'alias': msg.substituteTrivial(original_cst.alias)
|
||||
})
|
||||
|
@ -131,7 +131,7 @@ class OperationTargetSerializer(serializers.Serializer):
|
|||
def validate(self, attrs):
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
operation = cast(Operation, attrs['target'])
|
||||
if oss and operation.oss != oss:
|
||||
if oss and operation.oss_id != oss.pk:
|
||||
raise serializers.ValidationError({
|
||||
'target': msg.operationNotInOSS(oss.title)
|
||||
})
|
||||
|
@ -155,7 +155,7 @@ class SetOperationInputSerializer(serializers.Serializer):
|
|||
def validate(self, attrs):
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
operation = cast(Operation, attrs['target'])
|
||||
if oss and operation.oss != oss:
|
||||
if oss and operation.oss_id != oss.pk:
|
||||
raise serializers.ValidationError({
|
||||
'target': msg.operationNotInOSS(oss.title)
|
||||
})
|
||||
|
|
|
@ -4,11 +4,17 @@ from typing import Optional, cast
|
|||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import PrimaryKeyRelatedField as PKField
|
||||
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.library.serializers import LibraryItemBaseSerializer, LibraryItemDetailsSerializer
|
||||
from apps.library.serializers import (
|
||||
LibraryItemBaseSerializer,
|
||||
LibraryItemDetailsSerializer,
|
||||
LibraryItemReferenceSerializer
|
||||
)
|
||||
from apps.oss.models import Inheritance
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import Constituenta, CstType, RSForm
|
||||
|
@ -90,6 +96,12 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
items = serializers.ListField(
|
||||
child=CstSerializer()
|
||||
)
|
||||
inheritance = serializers.ListField(
|
||||
child=serializers.ListField(child=serializers.IntegerField())
|
||||
)
|
||||
oss = serializers.ListField(
|
||||
child=LibraryItemReferenceSerializer()
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -101,6 +113,15 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
result['items'] = []
|
||||
for cst in RSForm(instance).constituents().order_by('order'):
|
||||
result['items'].append(CstSerializer(cst).data)
|
||||
result['inheritance'] = []
|
||||
for link in Inheritance.objects.filter(Q(child__schema=instance) | Q(parent__schema=instance)):
|
||||
result['inheritance'].append([link.child.pk, link.parent.pk])
|
||||
result['oss'] = []
|
||||
for oss in LibraryItem.objects.filter(items__result=instance).only('alias'):
|
||||
result['oss'].append({
|
||||
'id': oss.pk,
|
||||
'alias': oss.alias
|
||||
})
|
||||
return result
|
||||
|
||||
def to_versioned_data(self) -> dict:
|
||||
|
@ -109,6 +130,8 @@ class RSFormSerializer(serializers.ModelSerializer):
|
|||
del result['versions']
|
||||
del result['subscribers']
|
||||
del result['editors']
|
||||
del result['inheritance']
|
||||
del result['oss']
|
||||
|
||||
del result['owner']
|
||||
del result['visible']
|
||||
|
@ -210,7 +233,7 @@ class CstTargetSerializer(serializers.Serializer):
|
|||
def validate(self, attrs):
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
cst = cast(Constituenta, attrs['target'])
|
||||
if schema and cst.schema != schema:
|
||||
if schema and cst.schema_id != schema.pk:
|
||||
raise serializers.ValidationError({
|
||||
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
|
@ -224,7 +247,7 @@ class CstTargetSerializer(serializers.Serializer):
|
|||
|
||||
class CstRenameSerializer(serializers.Serializer):
|
||||
''' Serializer: Constituenta renaming. '''
|
||||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
target = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
|
||||
alias = serializers.CharField()
|
||||
cst_type = serializers.CharField()
|
||||
|
||||
|
@ -232,7 +255,7 @@ class CstRenameSerializer(serializers.Serializer):
|
|||
attrs = super().validate(attrs)
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
cst = cast(Constituenta, attrs['target'])
|
||||
if cst.schema != schema:
|
||||
if cst.schema_id != schema.pk:
|
||||
raise serializers.ValidationError({
|
||||
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
|
@ -258,7 +281,7 @@ class CstListSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
for item in attrs['items']:
|
||||
if item.schema != schema:
|
||||
if item.schema_id != schema.pk:
|
||||
raise serializers.ValidationError({
|
||||
f'{item.pk}': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
|
@ -272,8 +295,8 @@ class CstMoveSerializer(CstListSerializer):
|
|||
|
||||
class SubstitutionSerializerBase(serializers.Serializer):
|
||||
''' Serializer: Basic substitution. '''
|
||||
original = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
substitution = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
|
||||
substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
|
||||
|
||||
|
||||
class CstSubstituteSerializer(serializers.Serializer):
|
||||
|
@ -297,11 +320,11 @@ class CstSubstituteSerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError({
|
||||
'alias': msg.substituteTrivial(original_cst.alias)
|
||||
})
|
||||
if original_cst.schema != schema:
|
||||
if original_cst.schema_id != schema.pk:
|
||||
raise serializers.ValidationError({
|
||||
'original': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
if substitution_cst.schema != schema:
|
||||
if substitution_cst.schema_id != schema.pk:
|
||||
raise serializers.ValidationError({
|
||||
'substitution': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
|
@ -329,7 +352,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
|||
})
|
||||
constituents = cast(list[Constituenta], attrs['items'])
|
||||
for cst in constituents:
|
||||
if cst.schema != schema_in:
|
||||
if cst.schema_id != schema_in.pk:
|
||||
raise serializers.ValidationError({
|
||||
f'{cst.pk}': msg.constituentaNotInRSform(schema_in.title)
|
||||
})
|
||||
|
@ -337,12 +360,12 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
|||
for item in attrs['substitutions']:
|
||||
original_cst = cast(Constituenta, item['original'])
|
||||
substitution_cst = cast(Constituenta, item['substitution'])
|
||||
if original_cst.schema == schema_in:
|
||||
if original_cst.schema_id == schema_in.pk:
|
||||
if original_cst not in constituents:
|
||||
raise serializers.ValidationError({
|
||||
f'{original_cst.pk}': msg.substitutionNotInList()
|
||||
})
|
||||
if substitution_cst.schema != schema_out:
|
||||
if substitution_cst.schema_id != schema_out.pk:
|
||||
raise serializers.ValidationError({
|
||||
f'{substitution_cst.pk}': msg.constituentaNotInRSform(schema_out.title)
|
||||
})
|
||||
|
@ -351,7 +374,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError({
|
||||
f'{substitution_cst.pk}': msg.substitutionNotInList()
|
||||
})
|
||||
if original_cst.schema != schema_out:
|
||||
if original_cst.schema_id != schema_out.pk:
|
||||
raise serializers.ValidationError({
|
||||
f'{original_cst.pk}': msg.constituentaNotInRSform(schema_out.title)
|
||||
})
|
||||
|
|
|
@ -102,6 +102,8 @@ class TestRSFormViewset(EndpointTester):
|
|||
self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved)
|
||||
self.assertEqual(response.data['subscribers'], [self.user.pk])
|
||||
self.assertEqual(response.data['editors'], [])
|
||||
self.assertEqual(response.data['inheritance'], [])
|
||||
self.assertEqual(response.data['oss'], [])
|
||||
|
||||
self.executeOK(item=self.unowned_id)
|
||||
self.executeForbidden(item=self.private_id)
|
||||
|
|
|
@ -62,6 +62,7 @@ export { VscLibrary as IconLibrary } from 'react-icons/vsc';
|
|||
export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
|
||||
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
||||
export { GiHoneycomb as IconOSS } from 'react-icons/gi';
|
||||
export { LuBaby as IconChild } from 'react-icons/lu';
|
||||
export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
|
||||
export { LuArchive as IconArchive } from 'react-icons/lu';
|
||||
export { LuDatabase as IconDatabase } from 'react-icons/lu';
|
||||
|
|
|
@ -20,6 +20,7 @@ function BadgeConstituenta({ value, prefixID, theme }: BadgeConstituentaProps) {
|
|||
'min-w-[3.1rem] max-w-[3.1rem]', // prettier: split lines
|
||||
'px-1',
|
||||
'border rounded-md',
|
||||
value.is_inherited && 'border-dashed',
|
||||
'text-center font-medium whitespace-nowrap'
|
||||
)}
|
||||
style={{
|
||||
|
|
|
@ -13,7 +13,10 @@ interface InfoConstituentaProps extends CProps.Div {
|
|||
function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) {
|
||||
return (
|
||||
<div className={clsx('dense min-w-[15rem]', className)} {...restProps}>
|
||||
<h2>Конституента {data.alias}</h2>
|
||||
<h2>
|
||||
Конституента {data.alias}
|
||||
{data.is_inherited ? ' (наследуется)' : ''}
|
||||
</h2>
|
||||
{data.term_resolved ? (
|
||||
<p>
|
||||
<b>Термин: </b>
|
||||
|
|
|
@ -102,8 +102,6 @@ function PickSubstitutions({
|
|||
};
|
||||
const toDelete = substitutions.map(item => item.original);
|
||||
const replacements = substitutions.map(item => item.substitution);
|
||||
console.log(toDelete, replacements);
|
||||
console.log(newSubstitution);
|
||||
if (
|
||||
toDelete.includes(newSubstitution.original) ||
|
||||
toDelete.includes(newSubstitution.substitution) ||
|
||||
|
|
|
@ -59,6 +59,8 @@ export class RSFormLoader {
|
|||
}
|
||||
|
||||
private inferCstAttributes() {
|
||||
const inherit_children = new Set(this.schema.inheritance.map(item => item[0]));
|
||||
const inherit_parents = new Set(this.schema.inheritance.map(item => item[1]));
|
||||
this.graph.topologicalOrder().forEach(cstID => {
|
||||
const cst = this.cstByID.get(cstID)!;
|
||||
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
|
||||
|
@ -66,6 +68,8 @@ export class RSFormLoader {
|
|||
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
|
||||
cst.children = [];
|
||||
cst.children_alias = [];
|
||||
cst.is_inherited = inherit_children.has(cst.id);
|
||||
cst.is_inherited_parent = inherit_parents.has(cst.id);
|
||||
cst.is_simple_expression = this.inferSimpleExpression(cst);
|
||||
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
|
||||
return;
|
||||
|
@ -165,6 +169,7 @@ export class RSFormLoader {
|
|||
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
|
||||
0
|
||||
),
|
||||
count_inherited: items.reduce((sum, cst) => sum + ((cst as IConstituenta).is_inherited ? 1 : 0), 0),
|
||||
|
||||
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
|
||||
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),
|
||||
|
|
|
@ -75,7 +75,7 @@ export interface ILibraryItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents library item constant data loaded for both OSS and RSForm.
|
||||
* Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
|
||||
*/
|
||||
export interface ILibraryItemData extends ILibraryItem {
|
||||
subscribers: UserID[];
|
||||
|
@ -83,7 +83,12 @@ export interface ILibraryItemData extends ILibraryItem {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents library item extended data with versions.
|
||||
* Represents {@link ILibraryItem} minimal reference data.
|
||||
*/
|
||||
export interface ILibraryItemReference extends Pick<ILibraryItem, 'id' | 'alias'> {}
|
||||
|
||||
/**
|
||||
* Represents {@link ILibraryItem} extended data with versions.
|
||||
*/
|
||||
export interface ILibraryItemVersioned extends ILibraryItemData {
|
||||
version?: VersionID;
|
||||
|
@ -91,7 +96,7 @@ export interface ILibraryItemVersioned extends ILibraryItemData {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents common library item editor controller.
|
||||
* Represents common {@link ILibraryItem} editor controller.
|
||||
*/
|
||||
export interface ILibraryItemEditor {
|
||||
schema?: ILibraryItemData;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { Graph } from '@/models/Graph';
|
||||
|
||||
import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library';
|
||||
import { ILibraryItem, ILibraryItemReference, ILibraryItemVersioned, LibraryItemID } from './library';
|
||||
import { ICstSubstitute } from './oss';
|
||||
import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang';
|
||||
|
||||
|
@ -111,6 +111,8 @@ export interface IConstituenta extends IConstituentaData {
|
|||
status: ExpressionStatus;
|
||||
is_template: boolean;
|
||||
is_simple_expression: boolean;
|
||||
is_inherited: boolean;
|
||||
is_inherited_parent: boolean;
|
||||
parent?: ConstituentaID;
|
||||
parent_alias?: string;
|
||||
children: number[];
|
||||
|
@ -183,6 +185,7 @@ export interface IRSFormStats {
|
|||
count_errors: number;
|
||||
count_property: number;
|
||||
count_incalculable: number;
|
||||
count_inherited: number;
|
||||
|
||||
count_text_term: number;
|
||||
count_definition: number;
|
||||
|
@ -198,10 +201,19 @@ export interface IRSFormStats {
|
|||
count_theorem: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data for {@link IRSForm} provided by backend.
|
||||
*/
|
||||
export interface IRSFormData extends ILibraryItemVersioned {
|
||||
items: IConstituentaData[];
|
||||
inheritance: ConstituentaID[][];
|
||||
oss: ILibraryItemReference[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents formal explication for set of concepts.
|
||||
*/
|
||||
export interface IRSForm extends ILibraryItemVersioned {
|
||||
export interface IRSForm extends IRSFormData {
|
||||
items: IConstituenta[];
|
||||
stats: IRSFormStats;
|
||||
graph: Graph;
|
||||
|
@ -209,13 +221,6 @@ export interface IRSForm extends ILibraryItemVersioned {
|
|||
cstByID: Map<ConstituentaID, IConstituenta>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data for {@link IRSForm} provided by backend.
|
||||
*/
|
||||
export interface IRSFormData extends ILibraryItemVersioned {
|
||||
items: IConstituentaData[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used for cloning {@link IRSForm}.
|
||||
*/
|
||||
|
|
|
@ -133,6 +133,8 @@ export function createMockConstituenta(id: ConstituentaID, alias: string, commen
|
|||
definition_resolved: '',
|
||||
status: ExpressionStatus.INCORRECT,
|
||||
is_template: false,
|
||||
is_inherited: false,
|
||||
is_inherited_parent: false,
|
||||
cst_class: CstClass.DERIVED,
|
||||
parse: {
|
||||
status: ParsingStatus.INCORRECT,
|
||||
|
|
|
@ -21,7 +21,7 @@ function InputNode(node: OssNodeInternal) {
|
|||
<>
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
|
||||
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
|
||||
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
|
||||
<MiniButton
|
||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||
noHover
|
||||
|
|
|
@ -22,7 +22,7 @@ function OperationNode(node: OssNodeInternal) {
|
|||
<>
|
||||
<Handle type='source' position={Position.Bottom} />
|
||||
|
||||
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
|
||||
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
|
||||
<MiniButton
|
||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||
noHover
|
||||
|
|
|
@ -5,8 +5,10 @@ import { AnimatePresence } from 'framer-motion';
|
|||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { IconSave } from '@/components/Icons';
|
||||
import { IconChild, IconSave } from '@/components/Icons';
|
||||
import RefsInput from '@/components/RefsInput';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import SubmitButton from '@/components/ui/SubmitButton';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
|
@ -182,7 +184,7 @@ function FormConstituenta({
|
|||
}
|
||||
value={expression}
|
||||
activeCst={state}
|
||||
disabled={disabled}
|
||||
disabled={disabled || state?.is_inherited}
|
||||
toggleReset={toggleReset}
|
||||
onChange={newValue => setExpression(newValue)}
|
||||
setTypification={setTypification}
|
||||
|
@ -229,15 +231,26 @@ function FormConstituenta({
|
|||
Добавить комментарий
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
{!disabled || processing ? (
|
||||
<div className='self-center flex'>
|
||||
<SubmitButton
|
||||
key='cst_form_submit'
|
||||
id='cst_form_submit'
|
||||
text='Сохранить изменения'
|
||||
className='self-center'
|
||||
disabled={disabled || !isModified}
|
||||
icon={<IconSave size='1.25rem' />}
|
||||
/>
|
||||
{state?.is_inherited ? (
|
||||
<Overlay position='right-[-2rem]'>
|
||||
<MiniButton
|
||||
icon={<IconChild size='1.25rem' className='clr-text-red' />}
|
||||
disabled
|
||||
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'
|
||||
/>
|
||||
</Overlay>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
</form>
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { IconSave } from '@/components/Icons';
|
||||
import { IconOSS, IconSave } from '@/components/Icons';
|
||||
import SelectVersion from '@/components/select/SelectVersion';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
import DropdownButton from '@/components/ui/DropdownButton';
|
||||
import Label from '@/components/ui/Label';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import SubmitButton from '@/components/ui/SubmitButton';
|
||||
import TextArea from '@/components/ui/TextArea';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import useDropdown from '@/hooks/useDropdown';
|
||||
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
|
||||
import { limits, patterns } from '@/utils/constants';
|
||||
import { information } from '@/utils/labels';
|
||||
import { limits, patterns, prefixes } from '@/utils/constants';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
import ToolbarItemAccess from './ToolbarItemAccess';
|
||||
|
@ -26,8 +28,8 @@ interface FormRSFormProps {
|
|||
}
|
||||
|
||||
function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
||||
const { schema, update, processing } = useRSForm();
|
||||
const controller = useRSEdit();
|
||||
const schema = controller.schema;
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
|
@ -35,6 +37,8 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
|||
const [visible, setVisible] = useState(false);
|
||||
const [readOnly, setReadOnly] = useState(false);
|
||||
|
||||
const ossMenu = useDropdown();
|
||||
|
||||
useEffect(() => {
|
||||
if (!schema) {
|
||||
setIsModified(false);
|
||||
|
@ -85,9 +89,37 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
|||
visible: visible,
|
||||
read_only: readOnly
|
||||
};
|
||||
update(data, () => toast.success(information.changesSaved));
|
||||
controller.updateSchema(data);
|
||||
};
|
||||
|
||||
const ossSelector = useMemo(
|
||||
() =>
|
||||
schema && schema?.oss.length > 0 ? (
|
||||
<Overlay position='left-[12.5rem] top-[-0.4rem]'>
|
||||
<div ref={ossMenu.ref}>
|
||||
<MiniButton
|
||||
icon={<IconOSS size='1.25rem' className='icon-primary' />}
|
||||
noHover
|
||||
title='Связанные операционные схемы'
|
||||
hideTitle={ossMenu.isOpen}
|
||||
onClick={() => ossMenu.toggle()}
|
||||
/>
|
||||
<Dropdown isOpen={ossMenu.isOpen} className='mt-[-0.1rem]'>
|
||||
{schema.oss.map((reference, index) => (
|
||||
<DropdownButton
|
||||
className='min-w-[5rem]'
|
||||
key={`${prefixes.oss_list}${index}`}
|
||||
text={reference.alias}
|
||||
onClick={event => controller.viewOSS(reference.id, event.ctrlKey || event.metaKey)}
|
||||
/>
|
||||
))}
|
||||
</Dropdown>
|
||||
</div>
|
||||
</Overlay>
|
||||
) : null,
|
||||
[schema, ossMenu, controller]
|
||||
);
|
||||
|
||||
return (
|
||||
<form id={id} className={clsx('mt-1 min-w-[22rem] sm:w-[30rem]', 'flex flex-col pt-1')} onSubmit={handleSubmit}>
|
||||
<TextInput
|
||||
|
@ -99,6 +131,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
|||
disabled={!controller.isContentEditable}
|
||||
onChange={event => setTitle(event.target.value)}
|
||||
/>
|
||||
{ossSelector}
|
||||
<div className='flex justify-between w-full gap-3 mb-3'>
|
||||
<TextInput
|
||||
id='schema_alias'
|
||||
|
@ -143,7 +176,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
|||
<SubmitButton
|
||||
text='Сохранить изменения'
|
||||
className='self-center mt-4'
|
||||
loading={processing}
|
||||
loading={controller.isProcessing}
|
||||
disabled={!isModified}
|
||||
icon={<IconSave size='1.25rem' />}
|
||||
/>
|
||||
|
|
|
@ -15,12 +15,15 @@ function RSFormStats({ stats }: RSFormStatsProps) {
|
|||
<Divider margins='my-2' className='sm:hidden' />
|
||||
|
||||
<LabeledValue id='count_all' label='Всего конституент' text={stats.count_all} />
|
||||
<LabeledValue id='count_errors' label='Некорректных' text={stats.count_errors} />
|
||||
{stats.count_inherited !== 0 ? (
|
||||
<LabeledValue id='count_inherited' label='Наследованные' text={stats.count_inherited} />
|
||||
) : null}
|
||||
<LabeledValue id='count_errors' label='Некорректные' text={stats.count_errors} />
|
||||
{stats.count_property !== 0 ? (
|
||||
<LabeledValue id='count_property' label='Неразмерных' text={stats.count_property} />
|
||||
<LabeledValue id='count_property' label='Неразмерные' text={stats.count_property} />
|
||||
) : null}
|
||||
{stats.count_incalculable !== 0 ? (
|
||||
<LabeledValue id='count_incalculable' label='Невычислимых' text={stats.count_incalculable} />
|
||||
<LabeledValue id='count_incalculable' label='Невычислимые' text={stats.count_incalculable} />
|
||||
) : null}
|
||||
|
||||
<Divider margins='my-2' />
|
||||
|
|
|
@ -24,7 +24,14 @@ import DlgInlineSynthesis from '@/dialogs/DlgInlineSynthesis';
|
|||
import DlgRenameCst from '@/dialogs/DlgRenameCst';
|
||||
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
|
||||
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
|
||||
import { AccessPolicy, IVersionData, LocationHead, VersionID } from '@/models/library';
|
||||
import {
|
||||
AccessPolicy,
|
||||
ILibraryUpdateData,
|
||||
IVersionData,
|
||||
LibraryItemID,
|
||||
LocationHead,
|
||||
VersionID
|
||||
} from '@/models/library';
|
||||
import { ICstSubstituteData } from '@/models/oss';
|
||||
import {
|
||||
ConstituentaID,
|
||||
|
@ -56,6 +63,8 @@ export interface IRSEditContext {
|
|||
canProduceStructure: boolean;
|
||||
nothingSelected: boolean;
|
||||
|
||||
updateSchema: (data: ILibraryUpdateData) => void;
|
||||
|
||||
setOwner: (newOwner: UserID) => void;
|
||||
setAccessPolicy: (newPolicy: AccessPolicy) => void;
|
||||
promptEditors: () => void;
|
||||
|
@ -68,6 +77,7 @@ export interface IRSEditContext {
|
|||
toggleSelect: (target: ConstituentaID) => void;
|
||||
deselectAll: () => void;
|
||||
|
||||
viewOSS: (target: LibraryItemID, newTab?: boolean) => void;
|
||||
viewVersion: (version?: VersionID, newTab?: boolean) => void;
|
||||
createVersion: () => void;
|
||||
restoreVersion: () => void;
|
||||
|
@ -177,11 +187,21 @@ export const RSEditState = ({
|
|||
[model.schema, setAccessLevel, model.isOwned, user, adminMode]
|
||||
);
|
||||
|
||||
const updateSchema = useCallback(
|
||||
(data: ILibraryUpdateData) => model.update(data, () => toast.success(information.changesSaved)),
|
||||
[model]
|
||||
);
|
||||
|
||||
const viewVersion = useCallback(
|
||||
(version?: VersionID, newTab?: boolean) => router.push(urls.schema(model.itemID, version), newTab),
|
||||
[router, model]
|
||||
);
|
||||
|
||||
const viewOSS = useCallback(
|
||||
(target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab),
|
||||
[router]
|
||||
);
|
||||
|
||||
const createVersion = useCallback(() => {
|
||||
if (isModified && !promptUnsaved()) {
|
||||
return;
|
||||
|
@ -571,6 +591,7 @@ export const RSEditState = ({
|
|||
<RSEditContext.Provider
|
||||
value={{
|
||||
schema: model.schema,
|
||||
updateSchema,
|
||||
selected,
|
||||
isMutable,
|
||||
isContentEditable,
|
||||
|
@ -591,6 +612,7 @@ export const RSEditState = ({
|
|||
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
|
||||
deselectAll: () => setSelected([]),
|
||||
|
||||
viewOSS,
|
||||
viewVersion,
|
||||
createVersion,
|
||||
restoreVersion,
|
||||
|
|
|
@ -62,7 +62,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='flex border-b clr-input overflow-hidden'>
|
||||
<div className='flex border-b clr-input'>
|
||||
<SearchBar
|
||||
id='constituents_search'
|
||||
noBorder
|
||||
|
|
|
@ -139,6 +139,7 @@ export const globals = {
|
|||
*/
|
||||
export const prefixes = {
|
||||
page_size: 'page_size_',
|
||||
oss_list: 'oss_list_',
|
||||
cst_list: 'cst_list_',
|
||||
cst_inline_synth_list: 'cst_inline_synth_list_',
|
||||
cst_inline_synth_substitutes: 'cst_inline_synth_substitutes_',
|
||||
|
|
Loading…
Reference in New Issue
Block a user