diff --git a/rsconcept/backend/apps/library/serializers/__init__.py b/rsconcept/backend/apps/library/serializers/__init__.py index 3347a3cf..37a5df78 100644 --- a/rsconcept/backend/apps/library/serializers/__init__.py +++ b/rsconcept/backend/apps/library/serializers/__init__.py @@ -5,6 +5,7 @@ from .data_access import ( LibraryItemBaseSerializer, LibraryItemCloneSerializer, LibraryItemDetailsSerializer, + LibraryItemReferenceSerializer, LibraryItemSerializer, UsersListSerializer, UserTargetSerializer, diff --git a/rsconcept/backend/apps/library/serializers/data_access.py b/rsconcept/backend/apps/library/serializers/data_access.py index b91c37fa..fc43d7a6 100644 --- a/rsconcept/backend/apps/library/serializers/data_access.py +++ b/rsconcept/backend/apps/library/serializers/data_access.py @@ -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: diff --git a/rsconcept/backend/apps/oss/serializers/data_access.py b/rsconcept/backend/apps/oss/serializers/data_access.py index 97148598..e3935530 100644 --- a/rsconcept/backend/apps/oss/serializers/data_access.py +++ b/rsconcept/backend/apps/oss/serializers/data_access.py @@ -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) }) diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index e768c8e6..77b4e0cb 100644 --- a/rsconcept/backend/apps/rsform/serializers/data_access.py +++ b/rsconcept/backend/apps/rsform/serializers/data_access.py @@ -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) }) diff --git a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py index e8e8ecd5..152d497b 100644 --- a/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py +++ b/rsconcept/backend/apps/rsform/tests/s_views/t_rsforms.py @@ -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) diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index 6dac71f8..c9a507d1 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -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'; diff --git a/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx b/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx index 207868f6..f70ab2fd 100644 --- a/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx +++ b/rsconcept/frontend/src/components/info/BadgeConstituenta.tsx @@ -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={{ diff --git a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx index e59362ee..a192c1d2 100644 --- a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx +++ b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx @@ -13,7 +13,10 @@ interface InfoConstituentaProps extends CProps.Div { function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) { return (
-

Конституента {data.alias}

+

+ Конституента {data.alias} + {data.is_inherited ? ' (наследуется)' : ''} +

{data.term_resolved ? (

Термин: diff --git a/rsconcept/frontend/src/components/select/PickSubstitutions.tsx b/rsconcept/frontend/src/components/select/PickSubstitutions.tsx index 706dc334..46fa9a2d 100644 --- a/rsconcept/frontend/src/components/select/PickSubstitutions.tsx +++ b/rsconcept/frontend/src/components/select/PickSubstitutions.tsx @@ -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) || diff --git a/rsconcept/frontend/src/models/RSFormLoader.ts b/rsconcept/frontend/src/models/RSFormLoader.ts index ad27b56f..67a3f5f1 100644 --- a/rsconcept/frontend/src/models/RSFormLoader.ts +++ b/rsconcept/frontend/src/models/RSFormLoader.ts @@ -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), diff --git a/rsconcept/frontend/src/models/library.ts b/rsconcept/frontend/src/models/library.ts index 6ad1abd9..975689db 100644 --- a/rsconcept/frontend/src/models/library.ts +++ b/rsconcept/frontend/src/models/library.ts @@ -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 {} + +/** + * 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; diff --git a/rsconcept/frontend/src/models/rsform.ts b/rsconcept/frontend/src/models/rsform.ts index 2cc5d74e..387ccd73 100644 --- a/rsconcept/frontend/src/models/rsform.ts +++ b/rsconcept/frontend/src/models/rsform.ts @@ -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; } -/** - * Represents data for {@link IRSForm} provided by backend. - */ -export interface IRSFormData extends ILibraryItemVersioned { - items: IConstituentaData[]; -} - /** * Represents data, used for cloning {@link IRSForm}. */ diff --git a/rsconcept/frontend/src/models/rsformAPI.ts b/rsconcept/frontend/src/models/rsformAPI.ts index 66285f34..4befc5f6 100644 --- a/rsconcept/frontend/src/models/rsformAPI.ts +++ b/rsconcept/frontend/src/models/rsformAPI.ts @@ -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, diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx index 33fb9c1c..59c1ad26 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/InputNode.tsx @@ -21,7 +21,7 @@ function InputNode(node: OssNodeInternal) { <> - + } noHover diff --git a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx index 9aec428a..18e056f9 100644 --- a/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx +++ b/rsconcept/frontend/src/pages/OssPage/EditorOssGraph/OperationNode.tsx @@ -22,7 +22,7 @@ function OperationNode(node: OssNodeInternal) { <> - + } noHover diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx index 80bfbc90..5494c167 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorConstituenta/FormConstituenta.tsx @@ -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({ Добавить комментарий ) : null} + {!disabled || processing ? ( - } - /> +

+ } + /> + {state?.is_inherited ? ( + + } + disabled + titleHtml='Внимание!
Конституента имеет потомков
в операционной схеме синтеза' + /> +
+ ) : null} +
) : null} diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/FormRSForm.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/FormRSForm.tsx index 599ec506..efb7b10a 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/FormRSForm.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/FormRSForm.tsx @@ -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 ? ( + +
+ } + noHover + title='Связанные операционные схемы' + hideTitle={ossMenu.isOpen} + onClick={() => ossMenu.toggle()} + /> + + {schema.oss.map((reference, index) => ( + controller.viewOSS(reference.id, event.ctrlKey || event.metaKey)} + /> + ))} + +
+
+ ) : null, + [schema, ossMenu, controller] + ); + return (
setTitle(event.target.value)} /> + {ossSelector}
} /> diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/RSFormStats.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/RSFormStats.tsx index b3c097fd..a4fb09ac 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/RSFormStats.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/RSFormStats.tsx @@ -14,13 +14,16 @@ function RSFormStats({ stats }: RSFormStatsProps) {
- - + + {stats.count_inherited !== 0 ? ( + + ) : null} + {stats.count_property !== 0 ? ( - + ) : null} {stats.count_incalculable !== 0 ? ( - + ) : null} diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx index 1005641f..9aab98ae 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx @@ -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 = ({ (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])), deselectAll: () => setSelected([]), + viewOSS, viewVersion, createVersion, restoreVersion, diff --git a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx index aa298679..2d6fcbed 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/ViewConstituents/ConstituentsSearch.tsx @@ -62,7 +62,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt ); return ( -
+