Compare commits
9 Commits
07c2f1da2f
...
f86d847d64
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f86d847d64 | ||
![]() |
b60c1305bd | ||
![]() |
13b7b4d748 | ||
![]() |
eb5cb6945f | ||
![]() |
1305560679 | ||
![]() |
c864ec947b | ||
![]() |
7a4fceb9bf | ||
![]() |
e1a95e1d81 | ||
![]() |
cdefe97d98 |
32
TODO.txt
32
TODO.txt
|
@ -2,33 +2,39 @@
|
||||||
For more specific TODOs see comments in code
|
For more specific TODOs see comments in code
|
||||||
|
|
||||||
[Functionality - PROGRESS]
|
[Functionality - PROGRESS]
|
||||||
- Design first user experience
|
- OSS change propagation: Advanced features
|
||||||
- Private projects. Consider cooperative editing
|
|
||||||
|
|
||||||
|
|
||||||
[Functionality - PENDING]
|
[Functionality - PENDING]
|
||||||
- Search functionality for manuals
|
|
||||||
- User notifications on edit - consider spam prevention and change aggregation
|
|
||||||
- Static analyzer for RSForm as a whole: check term duplication and empty conventions
|
|
||||||
- Content based search in Library
|
|
||||||
- User profile: Settings + settings persistency
|
|
||||||
|
|
||||||
- Landing page
|
- Landing page
|
||||||
- Home page (user specific)
|
- Design first user experience
|
||||||
|
- Demo sandbox for anonymous users
|
||||||
|
|
||||||
|
- User profile: Settings + settings persistency
|
||||||
|
- Custom LibraryItem lists
|
||||||
|
- Custom user filters and sharing filters
|
||||||
|
|
||||||
|
- Static analyzer for RSForm as a whole: check term duplication and empty conventions
|
||||||
|
- OSS clone and versioning
|
||||||
|
|
||||||
|
- Focus on codemirror editor when label is clicked (need React 19 ref for clean code solution)
|
||||||
- Draggable rows in constituents table
|
- Draggable rows in constituents table
|
||||||
|
|
||||||
|
- replace reagraph with react-flow in TermGraph and FormulaGraph
|
||||||
|
- Search functionality for Help Manuals
|
||||||
- Export PDF (Items list, Graph)
|
- Export PDF (Items list, Graph)
|
||||||
- ARIA (accessibility considerations) - for now machine reading not supported
|
- ARIA (accessibility considerations) - for now machine reading not supported
|
||||||
- Internationalization - at least english version. Consider react.intl
|
- Internationalization - at least english version. Consider react.intl
|
||||||
- Focus on codemirror editor when label is clicked (need React 19 ref for clean code solution)
|
|
||||||
|
|
||||||
- Sitemap for better SEO and crawler optimization
|
- Sitemap for better SEO and crawler optimization
|
||||||
|
|
||||||
|
[Functionality - CANCELED]
|
||||||
|
- User notifications on edit - consider spam prevention and change aggregation
|
||||||
|
- Content based search in Library
|
||||||
|
- Home page (user specific)
|
||||||
|
- Private projects. Consider cooperative editing
|
||||||
|
|
||||||
[Tech]
|
[Tech]
|
||||||
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
|
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib
|
||||||
- add debounce to some search fields
|
- add debounce to some search fields. Consider pagination and dynamic loading
|
||||||
- DataTable: fixed percentage columns, especially for SubstituteTable. Rework column sizing mechanics
|
- DataTable: fixed percentage columns, especially for SubstituteTable. Rework column sizing mechanics
|
||||||
- move autopep8 and isort settings from vscode settings to pyproject.toml
|
- move autopep8 and isort settings from vscode settings to pyproject.toml
|
||||||
- Test UI for #enable-force-dark Chrome setting
|
- Test UI for #enable-force-dark Chrome setting
|
||||||
|
|
|
@ -66,8 +66,14 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
Operation.objects.bulk_update(update_list, ['alias', 'title', 'comment'])
|
Operation.objects.bulk_update(update_list, ['alias', 'title', 'comment'])
|
||||||
|
|
||||||
def perform_destroy(self, instance: m.LibraryItem) -> None:
|
def perform_destroy(self, instance: m.LibraryItem) -> None:
|
||||||
|
if instance.item_type == m.LibraryItemType.RSFORM:
|
||||||
PropagationFacade.before_delete_schema(instance)
|
PropagationFacade.before_delete_schema(instance)
|
||||||
return super().perform_destroy(instance)
|
super().perform_destroy(instance)
|
||||||
|
if instance.item_type == m.LibraryItemType.OPERATION_SCHEMA:
|
||||||
|
schemas = list(OperationSchema(instance).owned_schemas())
|
||||||
|
super().perform_destroy(instance)
|
||||||
|
for schema in schemas:
|
||||||
|
self.perform_destroy(schema)
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
if self.action in ['update', 'partial_update']:
|
if self.action in ['update', 'partial_update']:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
''' Testing API: Change attributes of OSS and RSForms. '''
|
''' Testing API: Change attributes of OSS and RSForms. '''
|
||||||
from apps.library.models import AccessPolicy, Editor, LocationHead
|
from apps.library.models import AccessPolicy, Editor, LibraryItem, LocationHead
|
||||||
from apps.oss.models import Operation, OperationSchema, OperationType
|
from apps.oss.models import Operation, OperationSchema, OperationType
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
@ -55,7 +55,7 @@ class TestChangeAttributes(EndpointTester):
|
||||||
)
|
)
|
||||||
self.owned.execute_operation(self.operation3)
|
self.owned.execute_operation(self.operation3)
|
||||||
self.operation3.refresh_from_db()
|
self.operation3.refresh_from_db()
|
||||||
self.ks3 = self.operation3.result
|
self.ks3 = RSForm(self.operation3.result)
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/set-owner', method='patch')
|
@decl_endpoint('/api/library/{item}/set-owner', method='patch')
|
||||||
|
@ -71,7 +71,7 @@ class TestChangeAttributes(EndpointTester):
|
||||||
self.assertEqual(self.owned.model.owner, self.user3)
|
self.assertEqual(self.owned.model.owner, self.user3)
|
||||||
self.assertEqual(self.ks1.model.owner, self.user)
|
self.assertEqual(self.ks1.model.owner, self.user)
|
||||||
self.assertEqual(self.ks2.model.owner, self.user2)
|
self.assertEqual(self.ks2.model.owner, self.user2)
|
||||||
self.assertEqual(self.ks3.owner, self.user3)
|
self.assertEqual(self.ks3.model.owner, self.user3)
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/set-location', method='patch')
|
@decl_endpoint('/api/library/{item}/set-location', method='patch')
|
||||||
def test_set_location(self):
|
def test_set_location(self):
|
||||||
|
@ -86,7 +86,7 @@ class TestChangeAttributes(EndpointTester):
|
||||||
self.assertEqual(self.owned.model.location, data['location'])
|
self.assertEqual(self.owned.model.location, data['location'])
|
||||||
self.assertNotEqual(self.ks1.model.location, data['location'])
|
self.assertNotEqual(self.ks1.model.location, data['location'])
|
||||||
self.assertNotEqual(self.ks2.model.location, data['location'])
|
self.assertNotEqual(self.ks2.model.location, data['location'])
|
||||||
self.assertEqual(self.ks3.location, data['location'])
|
self.assertEqual(self.ks3.model.location, data['location'])
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/set-access-policy', method='patch')
|
@decl_endpoint('/api/library/{item}/set-access-policy', method='patch')
|
||||||
def test_set_access_policy(self):
|
def test_set_access_policy(self):
|
||||||
|
@ -101,13 +101,13 @@ class TestChangeAttributes(EndpointTester):
|
||||||
self.assertEqual(self.owned.model.access_policy, data['access_policy'])
|
self.assertEqual(self.owned.model.access_policy, data['access_policy'])
|
||||||
self.assertNotEqual(self.ks1.model.access_policy, data['access_policy'])
|
self.assertNotEqual(self.ks1.model.access_policy, data['access_policy'])
|
||||||
self.assertNotEqual(self.ks2.model.access_policy, data['access_policy'])
|
self.assertNotEqual(self.ks2.model.access_policy, data['access_policy'])
|
||||||
self.assertEqual(self.ks3.access_policy, data['access_policy'])
|
self.assertEqual(self.ks3.model.access_policy, data['access_policy'])
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}/set-editors', method='patch')
|
@decl_endpoint('/api/library/{item}/set-editors', method='patch')
|
||||||
def test_set_editors(self):
|
def test_set_editors(self):
|
||||||
Editor.set(self.owned.model.pk, [self.user2.pk])
|
Editor.set(self.owned.model.pk, [self.user2.pk])
|
||||||
Editor.set(self.ks1.model.pk, [self.user2.pk, self.user.pk])
|
Editor.set(self.ks1.model.pk, [self.user2.pk, self.user.pk])
|
||||||
Editor.set(self.ks3.pk, [self.user2.pk, self.user.pk])
|
Editor.set(self.ks3.model.pk, [self.user2.pk, self.user.pk])
|
||||||
data = {'users': [self.user3.pk]}
|
data = {'users': [self.user3.pk]}
|
||||||
|
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
|
@ -119,7 +119,7 @@ class TestChangeAttributes(EndpointTester):
|
||||||
self.assertEqual(list(self.owned.model.editors()), [self.user3])
|
self.assertEqual(list(self.owned.model.editors()), [self.user3])
|
||||||
self.assertEqual(list(self.ks1.model.editors()), [self.user, self.user2])
|
self.assertEqual(list(self.ks1.model.editors()), [self.user, self.user2])
|
||||||
self.assertEqual(list(self.ks2.model.editors()), [])
|
self.assertEqual(list(self.ks2.model.editors()), [])
|
||||||
self.assertEqual(set(self.ks3.editors()), set([self.user, self.user3]))
|
self.assertEqual(set(self.ks3.model.editors()), set([self.user, self.user3]))
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}', method='patch')
|
@decl_endpoint('/api/library/{item}', method='patch')
|
||||||
def test_sync_from_result(self):
|
def test_sync_from_result(self):
|
||||||
|
@ -147,6 +147,13 @@ class TestChangeAttributes(EndpointTester):
|
||||||
|
|
||||||
response = self.executeOK(data=data, item=self.owned_id)
|
response = self.executeOK(data=data, item=self.owned_id)
|
||||||
self.ks3.refresh_from_db()
|
self.ks3.refresh_from_db()
|
||||||
self.assertEqual(self.ks3.alias, data['item_data']['alias'])
|
self.assertEqual(self.ks3.model.alias, data['item_data']['alias'])
|
||||||
self.assertEqual(self.ks3.title, data['item_data']['title'])
|
self.assertEqual(self.ks3.model.title, data['item_data']['title'])
|
||||||
self.assertEqual(self.ks3.comment, data['item_data']['comment'])
|
self.assertEqual(self.ks3.model.comment, data['item_data']['comment'])
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{item}', method='delete')
|
||||||
|
def test_destroy_oss_consequence(self):
|
||||||
|
response = self.executeNoContent(item=self.owned_id)
|
||||||
|
self.assertFalse(LibraryItem.objects.filter(pk=self.owned_id).exists())
|
||||||
|
self.assertFalse(LibraryItem.objects.filter(pk=self.ks3.model.pk).exists())
|
||||||
|
self.assertTrue(LibraryItem.objects.filter(pk=self.ks1.model.pk).exists())
|
||||||
|
|
|
@ -115,14 +115,19 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def to_representation(self, instance: LibraryItem) -> dict:
|
def to_representation(self, instance: LibraryItem) -> dict:
|
||||||
result = LibraryItemDetailsSerializer(instance).data
|
result = self.to_base_data(instance)
|
||||||
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)):
|
for link in Inheritance.objects.filter(Q(child__schema=instance) | Q(parent__schema=instance)):
|
||||||
result['inheritance'].append([link.child.pk, link.parent.pk])
|
result['inheritance'].append([link.child.pk, link.parent.pk])
|
||||||
|
return result
|
||||||
|
|
||||||
|
def to_base_data(self, instance: LibraryItem) -> dict:
|
||||||
|
''' Create serializable base representation without redundant data. '''
|
||||||
|
result = LibraryItemDetailsSerializer(instance).data
|
||||||
|
result['items'] = []
|
||||||
result['oss'] = []
|
result['oss'] = []
|
||||||
|
result['inheritance'] = []
|
||||||
|
for cst in RSForm(instance).constituents().order_by('order'):
|
||||||
|
result['items'].append(CstSerializer(cst).data)
|
||||||
for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'):
|
for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'):
|
||||||
result['oss'].append({
|
result['oss'].append({
|
||||||
'id': oss.pk,
|
'id': oss.pk,
|
||||||
|
@ -132,11 +137,9 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def to_versioned_data(self) -> dict:
|
def to_versioned_data(self) -> dict:
|
||||||
''' Create serializable version representation without redundant data. '''
|
''' Create serializable version representation without redundant data. '''
|
||||||
result = self.to_representation(cast(LibraryItem, self.instance))
|
result = self.to_base_data(cast(LibraryItem, self.instance))
|
||||||
del result['versions']
|
del result['versions']
|
||||||
del result['editors']
|
del result['editors']
|
||||||
del result['inheritance']
|
|
||||||
del result['oss']
|
|
||||||
|
|
||||||
del result['owner']
|
del result['owner']
|
||||||
del result['visible']
|
del result['visible']
|
||||||
|
@ -150,7 +153,7 @@ class RSFormSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def from_versioned_data(self, version: int, data: dict) -> dict:
|
def from_versioned_data(self, version: int, data: dict) -> dict:
|
||||||
''' Load data from version. '''
|
''' Load data from version. '''
|
||||||
result = self.to_representation(cast(LibraryItem, self.instance))
|
result = self.to_base_data(cast(LibraryItem, self.instance))
|
||||||
result['version'] = version
|
result['version'] = version
|
||||||
return result | data
|
return result | data
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ export { BiFirstPage as IconPageFirst } from 'react-icons/bi';
|
||||||
export { BiLastPage as IconPageLast } from 'react-icons/bi';
|
export { BiLastPage as IconPageLast } from 'react-icons/bi';
|
||||||
export { TbCalendarPlus as IconDateCreate } from 'react-icons/tb';
|
export { TbCalendarPlus as IconDateCreate } from 'react-icons/tb';
|
||||||
export { TbCalendarRepeat as IconDateUpdate } from 'react-icons/tb';
|
export { TbCalendarRepeat as IconDateUpdate } from 'react-icons/tb';
|
||||||
|
export { PiFileCsv as IconCSV } from 'react-icons/pi';
|
||||||
|
|
||||||
// ==== User status =======
|
// ==== User status =======
|
||||||
export { LuUserCircle2 as IconUser } from 'react-icons/lu';
|
export { LuUserCircle2 as IconUser } from 'react-icons/lu';
|
||||||
|
@ -68,9 +69,22 @@ export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
|
||||||
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
||||||
export { TbHexagons as IconOSS } from 'react-icons/tb';
|
export { TbHexagons as IconOSS } from 'react-icons/tb';
|
||||||
export { TbHexagon as IconRSForm } from 'react-icons/tb';
|
export { TbHexagon as IconRSForm } from 'react-icons/tb';
|
||||||
export { TbTopologyRing as IconConsolidation } from 'react-icons/tb';
|
export { TbAssembly as IconRSFormOwned } from 'react-icons/tb';
|
||||||
export { GrInherit as IconChild } from 'react-icons/gr';
|
export { TbBallFootball as IconRSFormImported } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterX as IconCstBaseSet } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterC as IconCstConstSet } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterS as IconCstStructured } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterA as IconCstAxiom } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterD as IconCstTerm } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterF as IconCstFunction } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterP as IconCstPredicate } from 'react-icons/tb';
|
||||||
|
export { TbHexagonLetterT as IconCstTheorem } from 'react-icons/tb';
|
||||||
|
export { LuNewspaper as IconDefinition } from 'react-icons/lu';
|
||||||
|
export { LuDna as IconTerminology } from 'react-icons/lu';
|
||||||
|
export { FaRegHandshake as IconConvention } from 'react-icons/fa6';
|
||||||
|
export { LiaCloneSolid as IconChild } from 'react-icons/lia';
|
||||||
export { RiParentLine as IconParent } from 'react-icons/ri';
|
export { RiParentLine as IconParent } from 'react-icons/ri';
|
||||||
|
export { TbTopologyRing as IconConsolidation } from 'react-icons/tb';
|
||||||
export { BiSpa as IconPredecessor } from 'react-icons/bi';
|
export { BiSpa as IconPredecessor } from 'react-icons/bi';
|
||||||
export { LuArchive as IconArchive } from 'react-icons/lu';
|
export { LuArchive as IconArchive } from 'react-icons/lu';
|
||||||
export { LuDatabase as IconDatabase } from 'react-icons/lu';
|
export { LuDatabase as IconDatabase } from 'react-icons/lu';
|
||||||
|
@ -95,7 +109,8 @@ export { RiShieldKeyholeLine as IconPrivate } from 'react-icons/ri';
|
||||||
export { BiBug as IconStatusError } from 'react-icons/bi';
|
export { BiBug as IconStatusError } from 'react-icons/bi';
|
||||||
export { BiCheckCircle as IconStatusOK } from 'react-icons/bi';
|
export { BiCheckCircle as IconStatusOK } from 'react-icons/bi';
|
||||||
export { BiHelpCircle as IconStatusUnknown } from 'react-icons/bi';
|
export { BiHelpCircle as IconStatusUnknown } from 'react-icons/bi';
|
||||||
export { BiPauseCircle as IconStatusIncalculable } from 'react-icons/bi';
|
export { BiStopCircle as IconStatusIncalculable } from 'react-icons/bi';
|
||||||
|
export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi';
|
||||||
export { LuPower as IconKeepAliasOn } from 'react-icons/lu';
|
export { LuPower as IconKeepAliasOn } from 'react-icons/lu';
|
||||||
export { LuPowerOff as IconKeepAliasOff } from 'react-icons/lu';
|
export { LuPowerOff as IconKeepAliasOff } from 'react-icons/lu';
|
||||||
|
|
||||||
|
@ -111,6 +126,7 @@ export { BiDuplicate as IconClone } from 'react-icons/bi';
|
||||||
export { LuReplace as IconReplace } from 'react-icons/lu';
|
export { LuReplace as IconReplace } from 'react-icons/lu';
|
||||||
export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
||||||
export { LuNetwork as IconGenerateStructure } from 'react-icons/lu';
|
export { LuNetwork as IconGenerateStructure } from 'react-icons/lu';
|
||||||
|
export { LuCombine as IconSynthesis } from 'react-icons/lu';
|
||||||
export { LuBookCopy as IconInlineSynthesis } from 'react-icons/lu';
|
export { LuBookCopy as IconInlineSynthesis } from 'react-icons/lu';
|
||||||
export { LuWand2 as IconGenerateNames } from 'react-icons/lu';
|
export { LuWand2 as IconGenerateNames } from 'react-icons/lu';
|
||||||
export { GrConnect as IconConnect } from 'react-icons/gr';
|
export { GrConnect as IconConnect } from 'react-icons/gr';
|
||||||
|
|
|
@ -151,7 +151,7 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
||||||
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>
|
<div className={clsx('flex flex-col gap-2', className, cursor)} style={style}>
|
||||||
<Label text={label} />
|
<Label text={label} />
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
className={'font-math'}
|
className='font-math'
|
||||||
id={id}
|
id={id}
|
||||||
ref={thisRef}
|
ref={thisRef}
|
||||||
basicSetup={editorSetup}
|
basicSetup={editorSetup}
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
import { CProps } from '../props';
|
|
||||||
import MiniButton from './MiniButton';
|
|
||||||
|
|
||||||
interface IconValueProps extends CProps.Styling, CProps.Titled {
|
|
||||||
id?: string;
|
|
||||||
icon: React.ReactNode;
|
|
||||||
value: string | number;
|
|
||||||
onClick?: (event: CProps.EventMouse) => void;
|
|
||||||
dense?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function IconValue({
|
|
||||||
id,
|
|
||||||
dense,
|
|
||||||
value,
|
|
||||||
icon,
|
|
||||||
disabled = true,
|
|
||||||
title,
|
|
||||||
titleHtml,
|
|
||||||
hideTitle,
|
|
||||||
className,
|
|
||||||
onClick,
|
|
||||||
...restProps
|
|
||||||
}: IconValueProps) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx('flex items-center', { 'justify-between gap-6 text-right': !dense, 'gap-2': dense }, className)}
|
|
||||||
{...restProps}
|
|
||||||
>
|
|
||||||
<MiniButton
|
|
||||||
noHover
|
|
||||||
noPadding
|
|
||||||
title={title}
|
|
||||||
titleHtml={titleHtml}
|
|
||||||
hideTitle={hideTitle}
|
|
||||||
icon={icon}
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
<span id={id}>{value}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default IconValue;
|
|
|
@ -46,7 +46,7 @@ function SelectorButton({
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{icon ? icon : null}
|
{icon ? icon : null}
|
||||||
{text ? <div className={'whitespace-nowrap'}>{text}</div> : null}
|
{text ? <div className='whitespace-nowrap'>{text}</div> : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
59
rsconcept/frontend/src/components/ui/ValueIcon.tsx
Normal file
59
rsconcept/frontend/src/components/ui/ValueIcon.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { globals } from '@/utils/constants';
|
||||||
|
|
||||||
|
import { CProps } from '../props';
|
||||||
|
import MiniButton from './MiniButton';
|
||||||
|
|
||||||
|
interface ValueIconProps extends CProps.Styling, CProps.Titled {
|
||||||
|
id?: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
value: string | number;
|
||||||
|
textClassName?: string;
|
||||||
|
onClick?: (event: CProps.EventMouse) => void;
|
||||||
|
smallThreshold?: number;
|
||||||
|
dense?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ValueIcon({
|
||||||
|
id,
|
||||||
|
dense,
|
||||||
|
icon,
|
||||||
|
value,
|
||||||
|
textClassName,
|
||||||
|
disabled = true,
|
||||||
|
title,
|
||||||
|
titleHtml,
|
||||||
|
hideTitle,
|
||||||
|
className,
|
||||||
|
smallThreshold,
|
||||||
|
onClick,
|
||||||
|
...restProps
|
||||||
|
}: ValueIconProps) {
|
||||||
|
const isSmall = useMemo(() => !smallThreshold || String(value).length < smallThreshold, [value, smallThreshold]);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'flex items-center',
|
||||||
|
'text-right',
|
||||||
|
'hover:cursor-default',
|
||||||
|
{ 'justify-between gap-6': !dense, 'gap-1': dense },
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...restProps}
|
||||||
|
data-tooltip-id={!!title || !!titleHtml ? globals.tooltip : undefined}
|
||||||
|
data-tooltip-html={titleHtml}
|
||||||
|
data-tooltip-content={title}
|
||||||
|
data-tooltip-hidden={hideTitle}
|
||||||
|
>
|
||||||
|
<MiniButton noHover noPadding icon={icon} disabled={disabled} onClick={onClick} />
|
||||||
|
<span id={id} className={clsx({ 'text-xs': !isSmall }, textClassName)}>
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ValueIcon;
|
|
@ -2,14 +2,14 @@ import clsx from 'clsx';
|
||||||
|
|
||||||
import { CProps } from '../props';
|
import { CProps } from '../props';
|
||||||
|
|
||||||
interface LabeledValueProps extends CProps.Styling {
|
interface ValueLabeledProps extends CProps.Styling {
|
||||||
id?: string;
|
id?: string;
|
||||||
label: string;
|
label: string;
|
||||||
text: string | number;
|
text: string | number;
|
||||||
title?: string;
|
title?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function LabeledValue({ id, label, text, title, className, ...restProps }: LabeledValueProps) {
|
function ValueLabeled({ id, label, text, title, className, ...restProps }: ValueLabeledProps) {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('flex justify-between gap-6', className)} {...restProps}>
|
<div className={clsx('flex justify-between gap-6', className)} {...restProps}>
|
||||||
<span title={title}>{label}</span>
|
<span title={title}>{label}</span>
|
||||||
|
@ -18,4 +18,4 @@ function LabeledValue({ id, label, text, title, className, ...restProps }: Label
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LabeledValue;
|
export default ValueLabeled;
|
16
rsconcept/frontend/src/components/ui/ValueStats.tsx
Normal file
16
rsconcept/frontend/src/components/ui/ValueStats.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
|
||||||
|
import { CProps } from '../props';
|
||||||
|
import ValueIcon from './ValueIcon';
|
||||||
|
|
||||||
|
interface ValueStatsProps extends CProps.Styling, CProps.Titled {
|
||||||
|
id: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
value: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ValueStats(props: ValueStatsProps) {
|
||||||
|
return <ValueIcon dense smallThreshold={PARAMETER.statSmallThreshold} textClassName='min-w-[1.4rem]' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ValueStats;
|
|
@ -65,7 +65,7 @@ function TabInputOperation({
|
||||||
<TextInput
|
<TextInput
|
||||||
id='operation_alias'
|
id='operation_alias'
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
className='w-[14rem]'
|
className='w-[16rem]'
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
disabled={attachedID !== undefined}
|
disabled={attachedID !== undefined}
|
||||||
|
|
|
@ -42,7 +42,7 @@ function TabSynthesisOperation({
|
||||||
<TextInput
|
<TextInput
|
||||||
id='operation_alias'
|
id='operation_alias'
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
className='w-[14rem]'
|
className='w-[16rem]'
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -24,7 +24,7 @@ function TabOperation({ alias, setAlias, title, setTitle, comment, setComment }:
|
||||||
<TextInput
|
<TextInput
|
||||||
id='operation_alias'
|
id='operation_alias'
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
className='w-[14rem]'
|
className='w-[16rem]'
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -42,7 +42,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
||||||
<Modal
|
<Modal
|
||||||
header='Переименование конституенты'
|
header='Переименование конституенты'
|
||||||
submitText='Переименовать'
|
submitText='Переименовать'
|
||||||
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
|
submitInvalidTooltip='Введите незанятое имя, соответствующее типу'
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={() => onRename(cstData)}
|
onSubmit={() => onRename(cstData)}
|
||||||
|
|
|
@ -30,7 +30,7 @@ function DlgSubstituteCst({ hideWindow, onSubstitute, schema }: DlgSubstituteCst
|
||||||
<Modal
|
<Modal
|
||||||
header='Отождествление'
|
header='Отождествление'
|
||||||
submitText='Отождествить'
|
submitText='Отождествить'
|
||||||
submitInvalidTooltip={'Выберите две различные конституенты'}
|
submitInvalidTooltip='Выберите две различные конституенты'
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={canSubmit}
|
canSubmit={canSubmit}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|
|
@ -90,7 +90,8 @@ export class OssLoader {
|
||||||
count_operations: items.length,
|
count_operations: items.length,
|
||||||
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||||
count_synthesis: items.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
count_synthesis: items.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
||||||
count_schemas: this.schemaIDs.length
|
count_schemas: this.schemaIDs.length,
|
||||||
|
count_owned: items.filter(item => !!item.result && item.is_owned).length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,7 @@ export interface IOperationSchemaStats {
|
||||||
count_inputs: number;
|
count_inputs: number;
|
||||||
count_synthesis: number;
|
count_synthesis: number;
|
||||||
count_schemas: number;
|
count_schemas: number;
|
||||||
|
count_owned: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -152,7 +152,7 @@ function FormCreateItem() {
|
||||||
required={!file}
|
required={!file}
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
placeholder={file && 'Загрузить из файла'}
|
placeholder={file && 'Загрузить из файла'}
|
||||||
className='w-[14rem]'
|
className='w-[16rem]'
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
import fileDownload from 'js-file-download';
|
||||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import { IconCSV } from '@/components/Icons';
|
||||||
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import DataLoader from '@/components/wrap/DataLoader';
|
import DataLoader from '@/components/wrap/DataLoader';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
|
@ -13,7 +17,7 @@ import { ILibraryItem, IRenameLocationData, LocationHead } from '@/models/librar
|
||||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||||
import { storage } from '@/utils/constants';
|
import { storage } from '@/utils/constants';
|
||||||
import { information } from '@/utils/labels';
|
import { information } from '@/utils/labels';
|
||||||
import { toggleTristateFlag } from '@/utils/utils';
|
import { convertToCSV, toggleTristateFlag } from '@/utils/utils';
|
||||||
|
|
||||||
import TableLibraryItems from './TableLibraryItems';
|
import TableLibraryItems from './TableLibraryItems';
|
||||||
import ToolbarSearch from './ToolbarSearch';
|
import ToolbarSearch from './ToolbarSearch';
|
||||||
|
@ -101,6 +105,19 @@ function LibraryPage() {
|
||||||
[location, library]
|
[location, library]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleDownloadCSV = useCallback(() => {
|
||||||
|
if (items.length === 0) {
|
||||||
|
toast.error(information.noDataToExport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blob = convertToCSV(items);
|
||||||
|
try {
|
||||||
|
fileDownload(blob, 'library.csv', 'text/csv;charset=utf-8;');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}, [items]);
|
||||||
|
|
||||||
const viewLibrary = useMemo(
|
const viewLibrary = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<TableLibraryItems
|
<TableLibraryItems
|
||||||
|
@ -142,6 +159,13 @@ function LibraryPage() {
|
||||||
hideWindow={() => setShowRenameLocation(false)}
|
hideWindow={() => setShowRenameLocation(false)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
<Overlay position='top-[0.25rem] right-0' layer='z-tooltip'>
|
||||||
|
<MiniButton
|
||||||
|
title='Выгрузить в формате CSV'
|
||||||
|
icon={<IconCSV size='1.25rem' className='icon-green' />}
|
||||||
|
onClick={handleDownloadCSV}
|
||||||
|
/>
|
||||||
|
</Overlay>
|
||||||
<ToolbarSearch
|
<ToolbarSearch
|
||||||
total={library.items.length ?? 0}
|
total={library.items.length ?? 0}
|
||||||
filtered={items.length}
|
filtered={items.length}
|
||||||
|
|
|
@ -37,13 +37,14 @@ function HelpMain() {
|
||||||
|
|
||||||
<h2>Разделы Справки</h2>
|
<h2>Разделы Справки</h2>
|
||||||
{[
|
{[
|
||||||
HelpTopic.INFO,
|
HelpTopic.THESAURUS,
|
||||||
HelpTopic.INTERFACE,
|
HelpTopic.INTERFACE,
|
||||||
HelpTopic.CONCEPTUAL,
|
HelpTopic.CONCEPTUAL,
|
||||||
HelpTopic.RSLANG,
|
HelpTopic.RSLANG,
|
||||||
HelpTopic.TERM_CONTROL,
|
HelpTopic.TERM_CONTROL,
|
||||||
HelpTopic.ACCESS,
|
HelpTopic.ACCESS,
|
||||||
HelpTopic.VERSIONS,
|
HelpTopic.VERSIONS,
|
||||||
|
HelpTopic.INFO,
|
||||||
HelpTopic.EXTEOR
|
HelpTopic.EXTEOR
|
||||||
].map(topic => (
|
].map(topic => (
|
||||||
<TopicItem key={`${prefixes.topic_item}${topic}`} topic={topic} />
|
<TopicItem key={`${prefixes.topic_item}${topic}`} topic={topic} />
|
||||||
|
@ -72,7 +73,7 @@ function HelpMain() {
|
||||||
версию браузера в случае возникновения визуальных ошибок или проблем с производительностью.
|
версию браузера в случае возникновения визуальных ошибок или проблем с производительностью.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Ваши пожелания по доработке, найденные ошибки и иные предложения можно направлять по email:{' '}
|
Ваши пожелания по доработке, найденные ошибки и иные предложения можно направлять на email:{' '}
|
||||||
<TextURL href={external_urls.mail_portal} text='portal@acconcept.ru' />
|
<TextURL href={external_urls.mail_portal} text='portal@acconcept.ru' />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,31 @@
|
||||||
import { IconChild, IconPredecessor, IconRSForm } from '@/components/Icons';
|
import {
|
||||||
|
IconChild,
|
||||||
|
IconConsolidation,
|
||||||
|
IconCstAxiom,
|
||||||
|
IconCstBaseSet,
|
||||||
|
IconCstConstSet,
|
||||||
|
IconCstFunction,
|
||||||
|
IconCstPredicate,
|
||||||
|
IconCstStructured,
|
||||||
|
IconCstTerm,
|
||||||
|
IconCstTheorem,
|
||||||
|
IconDownload,
|
||||||
|
IconGraphCollapse,
|
||||||
|
IconGraphExpand,
|
||||||
|
IconGraphInputs,
|
||||||
|
IconGraphOutputs,
|
||||||
|
IconOSS,
|
||||||
|
IconPredecessor,
|
||||||
|
IconRSForm,
|
||||||
|
IconRSFormImported,
|
||||||
|
IconRSFormOwned,
|
||||||
|
IconStatusError,
|
||||||
|
IconStatusIncalculable,
|
||||||
|
IconStatusOK,
|
||||||
|
IconStatusProperty,
|
||||||
|
IconStatusUnknown,
|
||||||
|
IconSynthesis
|
||||||
|
} from '@/components/Icons';
|
||||||
import LinkTopic from '@/components/ui/LinkTopic';
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
@ -12,11 +39,11 @@ function HelpThesaurus() {
|
||||||
Справки через гиперссылки. Также указываются графические обозначения (иконки, цвета), используемые для
|
Справки через гиперссылки. Также указываются графические обозначения (иконки, цвета), используемые для
|
||||||
обозначения соответствующих сущностей в интерфейсе Портала.
|
обозначения соответствующих сущностей в интерфейсе Портала.
|
||||||
</p>
|
</p>
|
||||||
<h2>Концептуализация</h2>
|
|
||||||
<p>Раздел в разработке...</p>
|
|
||||||
<h2>Концептуальная схема</h2>
|
<h2>Концептуальная схема</h2>
|
||||||
<p>
|
<p>
|
||||||
<IconRSForm size='1rem' className='inline-icon' />{' '}
|
<IconRSForm size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}
|
||||||
<LinkTopic text='Концептуальная схема' topic={HelpTopic.CC_SYSTEM} /> (<i>система определений, КС</i>) –
|
<LinkTopic text='Концептуальная схема' topic={HelpTopic.CC_SYSTEM} /> (<i>система определений, КС</i>) –
|
||||||
совокупность отдельных понятий и утверждений, а также связей между ними, задаваемых определениями.
|
совокупность отдельных понятий и утверждений, а также связей между ними, задаваемых определениями.
|
||||||
</p>
|
</p>
|
||||||
|
@ -28,6 +55,33 @@ function HelpThesaurus() {
|
||||||
Родоструктурная экспликация КС – экспликация КС с помощью{' '}
|
Родоструктурная экспликация КС – экспликация КС с помощью{' '}
|
||||||
<LinkTopic text='аппарата родов структур' topic={HelpTopic.RSLANG} />.
|
<LinkTopic text='аппарата родов структур' topic={HelpTopic.RSLANG} />.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
Граф термов – ориентированный граф, узлами которого являются конституенты КС, а связи задаются на основе
|
||||||
|
вхождения имени конституенты в определение другой конституенты.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Ядро концептуальной схемы – совокупность базовых понятий, аксиом и промежуточных производных понятий,
|
||||||
|
необходимых для формирования выражений аксиом. Остальные конституенты относят к Телу концептуальной схемы.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
По <b>отношению к операциям ОСС</b> выделены:
|
||||||
|
<li>
|
||||||
|
<IconRSForm size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}свободная КС – это КС не прикрепленная ни к одной операции в ОСС;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconRSFormOwned size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}собственная КС данной ОСС – это КС, прикрепленная к операции в ОСС, чьи владелец и расположение
|
||||||
|
совпадают с соответствующими атрибутами ОСС.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconRSFormImported size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}внешняя КС данной ОСС – это КС, прикрепленная к операции в ОСС, чьи владелец или расположение не
|
||||||
|
совпадают с соответствующими атрибутами ОСС;
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<h2>Конституента</h2>
|
<h2>Конституента</h2>
|
||||||
<p>
|
<p>
|
||||||
|
@ -37,13 +91,84 @@ function HelpThesaurus() {
|
||||||
являются Термин, Конвенция, Типизация (Структура), Формальное определение, Текстовое определение, Комментарий.
|
являются Термин, Конвенция, Типизация (Структура), Формальное определение, Текстовое определение, Комментарий.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
По <b>наличию формального определения в рамках КС</b> выделены:
|
По <b>характеру формального определения в рамках КС</b> выделены классы:
|
||||||
<li>
|
<li>
|
||||||
базовое понятие (<i>неопределяемое понятие</i>) не имеет определения и задано конвенцией и аксиомами;
|
базовое понятие (<i>неопределяемое понятие</i>) не имеет определения и задано конвенцией и аксиомами;
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
производное понятие (<i>выводимое понятие</i>) имеет определение.
|
производное понятие (<i>выводимое понятие</i>) имеет определение.
|
||||||
</li>
|
</li>
|
||||||
|
<li>утверждение определяется через логическое выражение.</li>
|
||||||
|
<li>шаблон определения содержит несвязанный параметр в определении.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
По <b>назначению</b> выделены типы конституент:
|
||||||
|
<li>
|
||||||
|
<IconCstBaseSet size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}базисное множество (X#) представляет неопределяемое понятие, представленное структурой множества,
|
||||||
|
чьи элементы различимы и не сравнимы с элементами других базисных множеств;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstConstSet size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}константное множество (C#) представляет неопределяемое понятие, моделируемое термом теории множеств,
|
||||||
|
который поддерживает ряд формальных операций над его элементами;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstStructured size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}родовая структура (S#) представляет неопределяемое понятие, имеющее определенную структуру,
|
||||||
|
построенную на базисных множествах и константных множеств. Содержание родовой структуры формируется{' '}
|
||||||
|
<LinkTopic text='отношением типизации' topic={HelpTopic.RSL_TYPES} />, аксиомами и конвенцией;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstAxiom size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}аксиома (A#) представляет утверждение, ограничивающее неопределяемые понятия и выводимые термы.
|
||||||
|
Интерпретация аксиомы должна быть истинна и является критерием корректности интерпретации КС в целом;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstTerm size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}терм (D#) представляет выводимое понятие через формальное определение;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstFunction size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}терм-функция (F#) представляет выводимое понятие (возможно параметризованное), имеющее характер
|
||||||
|
функционального отношения между набором аргументов и результатом;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstPredicate size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}предикат-функция (P#) представляет выводимое понятие (возможно параметризованное), имеющее характер
|
||||||
|
логического выражения, проверяющее заданные аргументы на соответствие некоторому условию;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCstTheorem size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}теорема (T#) представляет ценное для предметной утверждение, значение которого может быть как
|
||||||
|
истинным так и ложным;
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
По <b>графу термов</b> выделены:
|
||||||
|
<li>
|
||||||
|
<IconGraphOutputs size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}потребители данной конституенты – конституенты, определения которых используют данную конституенту
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconGraphInputs size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}поставщики данной конституенты – конституенты, имена которых используются в определении данной
|
||||||
|
конституенты
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconGraphExpand size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}зависимые от данной конституенты – потребители данной конституенты напрямую или по цепочке
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconGraphCollapse size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}влияющие на данную конституенту – поставщики данной конституенты напрямую или по цепочке
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
@ -63,6 +188,33 @@ function HelpThesaurus() {
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
Для характеристики <b>корректности определения</b> введены статусы конституент:
|
||||||
|
<li>
|
||||||
|
<IconStatusUnknown size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}не проверено – требуется проверка формального определения (промежуточный статус);
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconStatusOK size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}корректно – формальное определение корректно;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconStatusError size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}ошибочно – ошибка в формальном определении;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconStatusProperty size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}неразмерное – формальное определение задает невычислимое множество, для которого возможно вычислить
|
||||||
|
предикат проверки принадлежности;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconStatusIncalculable size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}невычислимо – формальное определение невозможно интерпретировать напрямую;
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
Для описания <b>отождествления</b> введены:
|
Для описания <b>отождествления</b> введены:
|
||||||
<li>отождествляемые конституенты – конституенты, состоящие в отождествлении;</li>
|
<li>отождествляемые конституенты – конституенты, состоящие в отождествлении;</li>
|
||||||
|
@ -78,40 +230,53 @@ function HelpThesaurus() {
|
||||||
<ul>
|
<ul>
|
||||||
Для описания <b>наследования</b> конституент в рамках ОСС введены:
|
Для описания <b>наследования</b> конституент в рамках ОСС введены:
|
||||||
<li>
|
<li>
|
||||||
<IconChild size='1rem' className='inline-icon' /> наследованная конституента – конституента, перенесенная из
|
<IconChild size='1rem' className='inline-icon' />
|
||||||
другой КС в рамках операции синтеза;
|
{'\u2009'}наследованная конституента – конституента, перенесенная из другой КС в рамках операции синтеза;
|
||||||
</li>
|
</li>
|
||||||
<li>собственная конституента – конституента, не являющаяся наследником других конституент;</li>
|
|
||||||
<li>
|
<li>
|
||||||
<IconPredecessor size='1rem' className='inline-icon' /> исходная конституента для данной конституенты –
|
<IconPredecessor size='1rem' className='inline-icon' />
|
||||||
собственная конституента, прямым или опосредованным наследником которой является данная конституента.
|
{'\u2009'}собственная конституента – конституента, не являющаяся наследником других конституент;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconPredecessor size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}исходная конституента для данной конституенты – собственная конституента, прямым или опосредованным
|
||||||
|
наследником которой является данная конституента.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Операционная схема синтеза</h2>
|
||||||
|
<p>
|
||||||
|
<IconOSS size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}
|
||||||
|
<LinkTopic text='Операционная схема синтеза' topic={HelpTopic.CC_OSS} /> (ОСС) – система концептуальных схем,
|
||||||
|
связанных операциями синтеза.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Граф синтеза – ориентированный граф, вершинами которого являются операции, а ребра указывают на использование
|
||||||
|
результата одной операции как аргумента другой операции.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Операция</h2>
|
||||||
|
<p>Операция – выделенная часть ОСС, определяющая способ получения КС в рамках ОСС.</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
По <b>способу получения КС выделены</b>:
|
||||||
|
<li>
|
||||||
|
<IconDownload size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}загрузка КС из библиотеки;
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconSynthesis size='1rem' className='inline-icon' />
|
||||||
|
{'\u2009'}синтез концептуальных схем.ыф
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<ul>
|
<p>
|
||||||
По <b>назначению</b> выделены:
|
<IconConsolidation className='inline-icon' />
|
||||||
<li>
|
{'\u2009'}Ромбовидный синтез – операция, где используются КС, имеющие общих предков.
|
||||||
базисное множество (X1) задает неопределяемое понятие, представленное структурой множества, чьи элементы
|
</p>
|
||||||
различимы и не сравнимы с элементами других базисных множеств;
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
константное множество (C1) задает неопределяемое понятие, моделируемое термом теории множеств, который
|
|
||||||
поддерживает ряд формальных операций над его элементами;
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
родовая структура (S1) задает неопределяемое понятие, имеющее определенную структуру, построенную на базисных
|
|
||||||
множествах и константных множеств. Содержание родовой структуры формируется{' '}
|
|
||||||
<LinkTopic text='отношением типизации' topic={HelpTopic.RSL_TYPES} />, аксиомами и конвенцией;
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Операционная схема синтеза</h2>
|
|
||||||
<p>Раздел в разработке...</p>
|
|
||||||
|
|
||||||
<h2>Операция</h2>
|
|
||||||
<p>Раздел в разработке...</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import { IconConsolidation, IconExecute, IconOSS } from '@/components/Icons';
|
import {
|
||||||
|
IconConsolidation,
|
||||||
|
IconDownload,
|
||||||
|
IconExecute,
|
||||||
|
IconOSS,
|
||||||
|
IconRSFormImported,
|
||||||
|
IconRSFormOwned,
|
||||||
|
IconSynthesis
|
||||||
|
} from '@/components/Icons';
|
||||||
import LinkTopic from '@/components/ui/LinkTopic';
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
@ -16,9 +24,24 @@ function HelpConceptOSS() {
|
||||||
и отображается в форме <LinkTopic text='Графа синтеза' topic={HelpTopic.UI_OSS_GRAPH} />.
|
и отображается в форме <LinkTopic text='Графа синтеза' topic={HelpTopic.UI_OSS_GRAPH} />.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Базовыми операциями ОСС являются загрузка и синтез. Схема может быть загружена из другой локации (
|
Базовыми операциями ОСС являются <IconDownload size='1rem' className='inline-icon' /> загрузка и{' '}
|
||||||
<b>внешняя КС</b>) или создана в ОСС (<b>собственная КС</b>). Загрузка схем, полученных синтезом в других ОСС не
|
<IconSynthesis size='1rem' className='inline-icon' /> синтез. Схема может быть загружена из другой локации{' '}
|
||||||
допускается. Также запрещена повторная загрузка той же КС в рамках одной ОСС.
|
<span className='text-nowrap'>
|
||||||
|
(<IconRSFormImported size='1rem' className='inline-icon' />
|
||||||
|
<b>внешняя КС</b>)
|
||||||
|
</span>{' '}
|
||||||
|
или создана в ОСС{' '}
|
||||||
|
<span className='text-nowrap'>
|
||||||
|
(<IconRSFormOwned size='1rem' className='inline-icon' />
|
||||||
|
<b>собственная КС</b>)
|
||||||
|
</span>
|
||||||
|
. Загрузка схем, полученных синтезом в других ОСС не допускается. Также запрещена повторная загрузка той же КС в
|
||||||
|
рамках одной ОСС.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
При изменении расположения или владельца ОСС соответствующие атрибуты изменяются у собственных КС. Также при
|
||||||
|
удалении ОСС удаляются и все собственные КС. При удалении операции, собственная КС отвязывается от ОСС и
|
||||||
|
становится свободной КС.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Операция синтеза в рамках ОСС задаются набором операций-аргументов и <b>таблицей отождествлений</b> понятий из
|
Операция синтеза в рамках ОСС задаются набором операций-аргументов и <b>таблицей отождествлений</b> понятий из
|
||||||
|
|
|
@ -47,13 +47,16 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
|
||||||
onDestroy={onDestroy}
|
onDestroy={onDestroy}
|
||||||
controller={controller}
|
controller={controller}
|
||||||
/>
|
/>
|
||||||
<AnimateFade onKeyDown={handleInput} className={clsx('sm:w-fit mx-auto', 'flex flex-col sm:flex-row px-6')}>
|
<AnimateFade
|
||||||
|
onKeyDown={handleInput}
|
||||||
|
className={clsx('md:w-fit md:max-w-fit max-w-[32rem]', 'mx-auto ', 'flex flex-col md:flex-row px-6')}
|
||||||
|
>
|
||||||
<FlexColumn className='px-3'>
|
<FlexColumn className='px-3'>
|
||||||
<FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
|
<FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
|
||||||
<EditorLibraryItem item={schema} isModified={isModified} controller={controller} />
|
<EditorLibraryItem item={schema} isModified={isModified} controller={controller} />
|
||||||
</FlexColumn>
|
</FlexColumn>
|
||||||
|
|
||||||
<OssStats stats={schema?.stats} />
|
{schema ? <OssStats stats={schema.stats} /> : null}
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -100,7 +100,7 @@ function FormOSS({ id, isModified, setIsModified }: FormOSSProps) {
|
||||||
id='schema_alias'
|
id='schema_alias'
|
||||||
required
|
required
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
className='w-[14rem]'
|
className='w-[16rem]'
|
||||||
disabled={!controller.isMutable}
|
disabled={!controller.isMutable}
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
|
|
@ -1,26 +1,56 @@
|
||||||
import Divider from '@/components/ui/Divider';
|
import clsx from 'clsx';
|
||||||
import LabeledValue from '@/components/ui/LabeledValue';
|
|
||||||
|
import { IconDownload, IconRSForm, IconRSFormImported, IconRSFormOwned, IconSynthesis } from '@/components/Icons';
|
||||||
|
import ValueStats from '@/components/ui/ValueStats';
|
||||||
import { IOperationSchemaStats } from '@/models/oss';
|
import { IOperationSchemaStats } from '@/models/oss';
|
||||||
|
|
||||||
interface OssStatsProps {
|
interface OssStatsProps {
|
||||||
stats?: IOperationSchemaStats;
|
stats: IOperationSchemaStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OssStats({ stats }: OssStatsProps) {
|
function OssStats({ stats }: OssStatsProps) {
|
||||||
if (!stats) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col sm:gap-1 sm:ml-6 sm:mt-8 sm:w-[16rem]'>
|
<div
|
||||||
<Divider margins='my-2' className='sm:hidden' />
|
className={clsx(
|
||||||
|
'mt-3 md:ml-5 md:mt-8 md:w-[15rem] w-[20rem] h-min mx-auto', // prettier: split-lines
|
||||||
|
'grid grid-cols-3 gap-1 justify-items-end'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div id='count_operations' className='w-fit flex gap-3 hover:cursor-default '>
|
||||||
|
<span>Всего</span>
|
||||||
|
<span>{stats.count_operations}</span>
|
||||||
|
</div>
|
||||||
|
<ValueStats
|
||||||
|
id='count_inputs'
|
||||||
|
icon={<IconDownload size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_inputs}
|
||||||
|
title='Загрузка'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_synthesis'
|
||||||
|
icon={<IconSynthesis size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_synthesis}
|
||||||
|
title='Синтез'
|
||||||
|
/>
|
||||||
|
|
||||||
<LabeledValue id='count_all' label='Всего операций' text={stats.count_operations} />
|
<ValueStats
|
||||||
<LabeledValue id='count_inputs' label='Загрузка' text={stats.count_inputs} />
|
id='count_schemas'
|
||||||
<LabeledValue id='count_synthesis' label='Синтез' text={stats.count_synthesis} />
|
icon={<IconRSForm size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_schemas}
|
||||||
<Divider margins='my-2' />
|
title='Прикрепленные схемы'
|
||||||
|
/>
|
||||||
<LabeledValue id='count_schemas' label='Прикрепленные схемы' text={stats.count_schemas} />
|
<ValueStats
|
||||||
|
id='count_owned'
|
||||||
|
icon={<IconRSFormOwned size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_owned}
|
||||||
|
title='Собственные'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_imported'
|
||||||
|
icon={<IconRSFormImported size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_schemas - stats.count_owned}
|
||||||
|
title='Внешние'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
|
||||||
noBorder
|
noBorder
|
||||||
noOutline
|
noOutline
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
title={'Редактирование'}
|
title='Редактирование'
|
||||||
hideTitle={editMenu.isOpen}
|
hideTitle={editMenu.isOpen}
|
||||||
className='h-full px-2'
|
className='h-full px-2'
|
||||||
icon={<IconEdit2 size='1.25rem' className={controller.isMutable ? 'icon-green' : 'icon-red'} />}
|
icon={<IconEdit2 size='1.25rem' className={controller.isMutable ? 'icon-green' : 'icon-red'} />}
|
||||||
|
|
|
@ -91,7 +91,7 @@ function OssTabs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDestroySchema = useCallback(() => {
|
const onDestroySchema = useCallback(() => {
|
||||||
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
if (!schema || !window.confirm(prompts.deleteOSS)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
destroyItem(schema.id, () => {
|
destroyItem(schema.id, () => {
|
||||||
|
|
|
@ -79,7 +79,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='overflow-y-auto' style={{ maxHeight: panelHeight }}>
|
<div className='overflow-y-auto min-h-[20rem]' style={{ maxHeight: panelHeight }}>
|
||||||
<ToolbarConstituenta
|
<ToolbarConstituenta
|
||||||
activeCst={activeCst}
|
activeCst={activeCst}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -79,8 +79,8 @@ function ToolbarConstituenta({
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Создать конституенту после данной'
|
title='Создать конституенту после данной'
|
||||||
icon={<IconNewItem size={'1.25rem'} className='icon-green' />}
|
icon={<IconNewItem size='1.25rem' className='icon-green' />}
|
||||||
disabled={disabled}
|
disabled={!controller.isContentEditable || controller.isProcessing}
|
||||||
onClick={() => controller.createCst(activeCst?.cst_type, false)}
|
onClick={() => controller.createCst(activeCst?.cst_type, false)}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useIntl } from 'react-intl';
|
||||||
import { IconDateCreate, IconDateUpdate, IconEditor, IconFolder, IconOwner } from '@/components/Icons';
|
import { IconDateCreate, IconDateUpdate, IconEditor, IconFolder, IconOwner } from '@/components/Icons';
|
||||||
import InfoUsers from '@/components/info/InfoUsers';
|
import InfoUsers from '@/components/info/InfoUsers';
|
||||||
import SelectUser from '@/components/select/SelectUser';
|
import SelectUser from '@/components/select/SelectUser';
|
||||||
import IconValue from '@/components/ui/IconValue';
|
import ValueIcon from '@/components/ui/ValueIcon';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import Tooltip from '@/components/ui/Tooltip';
|
import Tooltip from '@/components/ui/Tooltip';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
|
@ -47,7 +47,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<IconValue
|
<ValueIcon
|
||||||
className='sm:mb-1 text-ellipsis max-w-[30rem]'
|
className='sm:mb-1 text-ellipsis max-w-[30rem]'
|
||||||
icon={<IconFolder size='1.25rem' className='icon-primary' />}
|
icon={<IconFolder size='1.25rem' className='icon-primary' />}
|
||||||
value={item.location}
|
value={item.location}
|
||||||
|
@ -68,7 +68,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
) : null}
|
) : null}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
) : null}
|
) : null}
|
||||||
<IconValue
|
<ValueIcon
|
||||||
className='sm:mb-1'
|
className='sm:mb-1'
|
||||||
icon={<IconOwner size='1.25rem' className='icon-primary' />}
|
icon={<IconOwner size='1.25rem' className='icon-primary' />}
|
||||||
value={getUserLabel(item.owner)}
|
value={getUserLabel(item.owner)}
|
||||||
|
@ -78,12 +78,11 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='sm:mb-1 flex justify-between items-center'>
|
<div className='sm:mb-1 flex justify-between items-center'>
|
||||||
<IconValue
|
<ValueIcon
|
||||||
id='editor_stats'
|
id='editor_stats'
|
||||||
dense
|
dense
|
||||||
icon={<IconEditor size='1.25rem' className='icon-primary' />}
|
icon={<IconEditor size='1.25rem' className='icon-primary' />}
|
||||||
value={item.editors.length}
|
value={item.editors.length}
|
||||||
title='Редакторы'
|
|
||||||
onClick={controller.promptEditors}
|
onClick={controller.promptEditors}
|
||||||
disabled={isModified || controller.isProcessing || accessLevel < UserLevel.OWNER}
|
disabled={isModified || controller.isProcessing || accessLevel < UserLevel.OWNER}
|
||||||
/>
|
/>
|
||||||
|
@ -91,7 +90,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} />
|
<InfoUsers items={item?.editors ?? []} prefix={prefixes.user_editors} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<IconValue
|
<ValueIcon
|
||||||
dense
|
dense
|
||||||
disabled
|
disabled
|
||||||
icon={<IconDateUpdate size='1.25rem' className='clr-text-green' />}
|
icon={<IconDateUpdate size='1.25rem' className='clr-text-green' />}
|
||||||
|
@ -99,7 +98,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
title='Дата обновления'
|
title='Дата обновления'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconValue
|
<ValueIcon
|
||||||
dense
|
dense
|
||||||
disabled
|
disabled
|
||||||
icon={<IconDateCreate size='1.25rem' className='clr-text-green' />}
|
icon={<IconDateCreate size='1.25rem' className='clr-text-green' />}
|
||||||
|
|
|
@ -20,7 +20,7 @@ interface EditorRSFormCardProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSFormCardProps) {
|
function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSFormCardProps) {
|
||||||
const { schema } = useRSForm();
|
const model = useRSForm();
|
||||||
const controller = useRSEdit();
|
const controller = useRSEdit();
|
||||||
|
|
||||||
function initiateSubmit() {
|
function initiateSubmit() {
|
||||||
|
@ -49,14 +49,14 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
|
||||||
/>
|
/>
|
||||||
<AnimateFade
|
<AnimateFade
|
||||||
onKeyDown={handleInput}
|
onKeyDown={handleInput}
|
||||||
className={clsx('sm:w-fit sm:max-w-fit max-w-[32rem]', 'mx-auto ', 'flex flex-col sm:flex-row px-6')}
|
className={clsx('md:w-fit md:max-w-fit max-w-[32rem] mx-auto', 'flex flex-col md:flex-row px-6')}
|
||||||
>
|
>
|
||||||
<FlexColumn className='flex-shrink'>
|
<FlexColumn className='flex-shrink'>
|
||||||
<FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
|
<FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />
|
||||||
<EditorLibraryItem item={schema} isModified={isModified} controller={controller} />
|
<EditorLibraryItem item={model.schema} isModified={isModified} controller={controller} />
|
||||||
</FlexColumn>
|
</FlexColumn>
|
||||||
|
|
||||||
<RSFormStats stats={schema?.stats} />
|
{model.schema ? <RSFormStats stats={model.schema.stats} isArchive={model.isArchive} /> : null}
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -100,7 +100,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
||||||
id='schema_alias'
|
id='schema_alias'
|
||||||
required
|
required
|
||||||
label='Сокращение'
|
label='Сокращение'
|
||||||
className='w-[14rem]'
|
className='w-[16rem]'
|
||||||
disabled={!controller.isContentEditable}
|
disabled={!controller.isContentEditable}
|
||||||
value={alias}
|
value={alias}
|
||||||
onChange={event => setAlias(event.target.value)}
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
|
|
@ -1,59 +1,151 @@
|
||||||
import Divider from '@/components/ui/Divider';
|
import clsx from 'clsx';
|
||||||
import LabeledValue from '@/components/ui/LabeledValue';
|
|
||||||
|
import {
|
||||||
|
IconChild,
|
||||||
|
IconConvention,
|
||||||
|
IconCstAxiom,
|
||||||
|
IconCstBaseSet,
|
||||||
|
IconCstConstSet,
|
||||||
|
IconCstFunction,
|
||||||
|
IconCstPredicate,
|
||||||
|
IconCstStructured,
|
||||||
|
IconCstTerm,
|
||||||
|
IconCstTheorem,
|
||||||
|
IconDefinition,
|
||||||
|
IconPredecessor,
|
||||||
|
IconStatusError,
|
||||||
|
IconStatusIncalculable,
|
||||||
|
IconStatusOK,
|
||||||
|
IconStatusProperty,
|
||||||
|
IconTerminology
|
||||||
|
} from '@/components/Icons';
|
||||||
|
import ValueStats from '@/components/ui/ValueStats';
|
||||||
import { type IRSFormStats } from '@/models/rsform';
|
import { type IRSFormStats } from '@/models/rsform';
|
||||||
|
|
||||||
interface RSFormStatsProps {
|
interface RSFormStatsProps {
|
||||||
stats?: IRSFormStats;
|
isArchive: boolean;
|
||||||
|
stats: IRSFormStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RSFormStats({ stats }: RSFormStatsProps) {
|
function RSFormStats({ stats, isArchive }: RSFormStatsProps) {
|
||||||
if (!stats) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col sm:gap-1 sm:ml-6 sm:mt-8 sm:w-[16rem]'>
|
<div
|
||||||
<Divider margins='my-2' className='sm:hidden' />
|
className={clsx(
|
||||||
|
'mt-3 md:ml-5 md:mt-8 md:w-[18rem] w-[25rem] h-min mx-auto', // prettier: split-lines
|
||||||
|
'grid grid-cols-4 gap-1 justify-items-end'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div id='count_all' className='col-span-2 w-fit flex gap-3 hover:cursor-default '>
|
||||||
|
<span>Всего</span>
|
||||||
|
<span>{stats.count_all}</span>
|
||||||
|
</div>
|
||||||
|
<ValueStats
|
||||||
|
id='count_owned'
|
||||||
|
icon={<IconPredecessor size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_all - stats.count_inherited}
|
||||||
|
title='Собственные'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_inherited'
|
||||||
|
icon={<IconChild size='1.25rem' className='clr-text-primary' />}
|
||||||
|
value={stats.count_inherited}
|
||||||
|
titleHtml={isArchive ? 'Архивные схемы не хранят<br/> информацию о наследовании' : 'Наследованные'}
|
||||||
|
/>
|
||||||
|
|
||||||
<LabeledValue id='count_all' label='Всего конституент' text={stats.count_all} />
|
<ValueStats
|
||||||
{stats.count_inherited !== 0 ? (
|
className='col-start-1'
|
||||||
<LabeledValue id='count_inherited' label='Наследованные' text={stats.count_inherited} />
|
id='count_ok'
|
||||||
) : null}
|
icon={<IconStatusOK size='1.25rem' className='clr-text-green' />}
|
||||||
<LabeledValue id='count_errors' label='Некорректные' text={stats.count_errors} />
|
value={stats.count_all - stats.count_errors - stats.count_property - stats.count_incalculable}
|
||||||
{stats.count_property !== 0 ? (
|
title='Корректные'
|
||||||
<LabeledValue id='count_property' label='Неразмерные' text={stats.count_property} />
|
/>
|
||||||
) : null}
|
<ValueStats
|
||||||
{stats.count_incalculable !== 0 ? (
|
id='count_property'
|
||||||
<LabeledValue id='count_incalculable' label='Невычислимые' text={stats.count_incalculable} />
|
icon={<IconStatusProperty size='1.25rem' className='clr-text-primary' />}
|
||||||
) : null}
|
value={stats.count_errors}
|
||||||
|
title='Неразмерные'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_incalculable'
|
||||||
|
icon={<IconStatusIncalculable size='1.25rem' className='clr-text-red' />}
|
||||||
|
value={stats.count_incalculable}
|
||||||
|
title='Невычислимые'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_errors'
|
||||||
|
icon={<IconStatusError size='1.25rem' className='clr-text-red' />}
|
||||||
|
value={stats.count_errors}
|
||||||
|
title='Некорректные'
|
||||||
|
/>
|
||||||
|
|
||||||
<Divider margins='my-2' />
|
<ValueStats
|
||||||
|
id='count_base'
|
||||||
|
icon={<IconCstBaseSet size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_base}
|
||||||
|
title='Базисные множества'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_constant'
|
||||||
|
icon={<IconCstConstSet size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_constant}
|
||||||
|
title='Константные множества'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_structured'
|
||||||
|
icon={<IconCstStructured size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_structured}
|
||||||
|
title='Родовые структуры'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_axiom'
|
||||||
|
icon={<IconCstAxiom size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_axiom}
|
||||||
|
title='Аксиомы'
|
||||||
|
/>
|
||||||
|
|
||||||
<LabeledValue id='count_text_term' label='Термины' text={stats.count_text_term} />
|
<ValueStats
|
||||||
<LabeledValue id='count_definition' label='Определения' text={stats.count_definition} />
|
id='count_term'
|
||||||
<LabeledValue id='count_convention' label='Конвенции' text={stats.count_convention} />
|
icon={<IconCstTerm size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_term}
|
||||||
|
title='Термы'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_function'
|
||||||
|
icon={<IconCstFunction size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_function}
|
||||||
|
title='Терм-функции'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_predicate'
|
||||||
|
icon={<IconCstPredicate size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_predicate}
|
||||||
|
title='Предикат-функции'
|
||||||
|
/>
|
||||||
|
<ValueStats
|
||||||
|
id='count_theorem'
|
||||||
|
icon={<IconCstTheorem size='1.25rem' className='clr-text-controls' />}
|
||||||
|
value={stats.count_theorem}
|
||||||
|
title='Теоремы'
|
||||||
|
/>
|
||||||
|
|
||||||
<Divider margins='my-2' />
|
<ValueStats
|
||||||
|
id='count_text_term'
|
||||||
{stats.count_base !== 0 ? (
|
icon={<IconTerminology size='1.25rem' className='clr-text-primary' />}
|
||||||
<LabeledValue id='count_base' label='Базисные множества ' text={stats.count_base} />
|
value={stats.count_text_term}
|
||||||
) : null}
|
title='Термины'
|
||||||
{stats.count_constant !== 0 ? (
|
/>
|
||||||
<LabeledValue id='count_constant' label='Константные множества ' text={stats.count_constant} />
|
<ValueStats
|
||||||
) : null}
|
id='count_definition'
|
||||||
{stats.count_structured !== 0 ? (
|
icon={<IconDefinition size='1.25rem' className='clr-text-primary' />}
|
||||||
<LabeledValue id='count_structured' label='Родовые структуры ' text={stats.count_structured} />
|
value={stats.count_definition}
|
||||||
) : null}
|
title='Определения'
|
||||||
{stats.count_axiom !== 0 ? <LabeledValue id='count_axiom' label='Аксиомы ' text={stats.count_axiom} /> : null}
|
/>
|
||||||
{stats.count_term !== 0 ? <LabeledValue id='count_term' label='Термы ' text={stats.count_term} /> : null}
|
<ValueStats
|
||||||
{stats.count_function !== 0 ? (
|
id='count_convention'
|
||||||
<LabeledValue id='count_function' label='Терм-функции ' text={stats.count_function} />
|
icon={<IconConvention size='1.25rem' className='clr-text-primary' />}
|
||||||
) : null}
|
value={stats.count_convention}
|
||||||
{stats.count_predicate !== 0 ? (
|
title='Конвенции'
|
||||||
<LabeledValue id='count_predicate' label='Предикат-функции ' text={stats.count_predicate} />
|
/>
|
||||||
) : null}
|
|
||||||
{stats.count_theorem !== 0 ? (
|
|
||||||
<LabeledValue id='count_theorem' label='Теоремы ' text={stats.count_theorem} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useLayoutEffect, useMemo, useState } from 'react';
|
import fileDownload from 'js-file-download';
|
||||||
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
import { IconCSV } from '@/components/Icons';
|
||||||
import SelectedCounter from '@/components/info/SelectedCounter';
|
import SelectedCounter from '@/components/info/SelectedCounter';
|
||||||
import { type RowSelectionState } from '@/components/ui/DataTable';
|
import { type RowSelectionState } from '@/components/ui/DataTable';
|
||||||
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { ConstituentaID, CstType } from '@/models/rsform';
|
import { ConstituentaID, CstType } from '@/models/rsform';
|
||||||
|
import { information } from '@/utils/labels';
|
||||||
|
import { convertToCSV } from '@/utils/utils';
|
||||||
|
|
||||||
import { useRSEdit } from '../RSEditContext';
|
import { useRSEdit } from '../RSEditContext';
|
||||||
import TableRSList from './TableRSList';
|
import TableRSList from './TableRSList';
|
||||||
|
@ -34,6 +41,19 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||||
}
|
}
|
||||||
}, [controller.selected, controller.schema]);
|
}, [controller.selected, controller.schema]);
|
||||||
|
|
||||||
|
const handleDownloadCSV = useCallback(() => {
|
||||||
|
if (!controller.schema || controller.schema.items.length === 0) {
|
||||||
|
toast.error(information.noDataToExport);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blob = convertToCSV(controller.schema.items);
|
||||||
|
try {
|
||||||
|
fileDownload(blob, `${controller.schema.alias}.csv`, 'text/csv;charset=utf-8;');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}, [controller]);
|
||||||
|
|
||||||
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
|
||||||
if (!controller.schema) {
|
if (!controller.schema) {
|
||||||
controller.deselectAll();
|
controller.deselectAll();
|
||||||
|
@ -121,6 +141,14 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Overlay position='top-[0.25rem] right-[1rem]' layer='z-tooltip'>
|
||||||
|
<MiniButton
|
||||||
|
title='Выгрузить в формате CSV'
|
||||||
|
icon={<IconCSV size='1.25rem' className='icon-green' />}
|
||||||
|
onClick={handleDownloadCSV}
|
||||||
|
/>
|
||||||
|
</Overlay>
|
||||||
|
|
||||||
<TableRSList
|
<TableRSList
|
||||||
items={controller.schema?.items}
|
items={controller.schema?.items}
|
||||||
maxHeight={tableHeight}
|
maxHeight={tableHeight}
|
||||||
|
|
|
@ -207,7 +207,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
noBorder
|
noBorder
|
||||||
noOutline
|
noOutline
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
title={'Редактирование'}
|
title='Редактирование'
|
||||||
hideTitle={editMenu.isOpen}
|
hideTitle={editMenu.isOpen}
|
||||||
className='h-full px-2'
|
className='h-full px-2'
|
||||||
icon={<IconEdit2 size='1.25rem' className={controller.isContentEditable ? 'icon-green' : 'icon-red'} />}
|
icon={<IconEdit2 size='1.25rem' className={controller.isContentEditable ? 'icon-green' : 'icon-red'} />}
|
||||||
|
|
|
@ -134,7 +134,7 @@ div:not(.dense) > p {
|
||||||
}
|
}
|
||||||
|
|
||||||
li::marker {
|
li::marker {
|
||||||
content: '– ';
|
content: '–\2009';
|
||||||
}
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* Global application Parameters. The place where magic numbers are put to rest.
|
* Global application Parameters. The place where magic numbers are put to rest.
|
||||||
*/
|
*/
|
||||||
export const PARAMETER = {
|
export const PARAMETER = {
|
||||||
smallScreen: 640, // == tailwind:xs
|
smallScreen: 640, // == tailwind:sm
|
||||||
smallTreeNodes: 50, // amount of nodes threshold for size increase for large graphs
|
smallTreeNodes: 50, // amount of nodes threshold for size increase for large graphs
|
||||||
refreshTimeout: 100, // milliseconds delay for post-refresh actions
|
refreshTimeout: 100, // milliseconds delay for post-refresh actions
|
||||||
minimalTimeout: 10, // milliseconds delay for fast updates
|
minimalTimeout: 10, // milliseconds delay for fast updates
|
||||||
|
@ -30,6 +30,8 @@ export const PARAMETER = {
|
||||||
ossLongLabel: 14, // characters - threshold for long labels - small font
|
ossLongLabel: 14, // characters - threshold for long labels - small font
|
||||||
ossTruncateLabel: 28, // characters - threshold for long labels - truncate
|
ossTruncateLabel: 28, // characters - threshold for long labels - truncate
|
||||||
|
|
||||||
|
statSmallThreshold: 3, // characters - threshold for small labels - small font
|
||||||
|
|
||||||
logicLabel: 'LOGIC',
|
logicLabel: 'LOGIC',
|
||||||
exteorVersion: '4.9.3',
|
exteorVersion: '4.9.3',
|
||||||
|
|
||||||
|
|
|
@ -933,6 +933,7 @@ export const information = {
|
||||||
versionRestored: 'Загрузка версии завершена',
|
versionRestored: 'Загрузка версии завершена',
|
||||||
locationRenamed: 'Ваши схемы перемещены',
|
locationRenamed: 'Ваши схемы перемещены',
|
||||||
cloneComplete: (alias: string) => `Копия создана: ${alias}`,
|
cloneComplete: (alias: string) => `Копия создана: ${alias}`,
|
||||||
|
noDataToExport: 'Нет данных для экспорта',
|
||||||
|
|
||||||
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
|
addedConstituents: (count: number) => `Добавлены конституенты: ${count}`,
|
||||||
newLibraryItem: 'Схема успешно создана',
|
newLibraryItem: 'Схема успешно создана',
|
||||||
|
@ -976,6 +977,8 @@ export const tooltips = {
|
||||||
export const prompts = {
|
export const prompts = {
|
||||||
promptUnsaved: 'Присутствуют несохраненные изменения. Продолжить без их учета?',
|
promptUnsaved: 'Присутствуют несохраненные изменения. Продолжить без их учета?',
|
||||||
deleteLibraryItem: 'Вы уверены, что хотите удалить данную схему?',
|
deleteLibraryItem: 'Вы уверены, что хотите удалить данную схему?',
|
||||||
|
deleteOSS:
|
||||||
|
'Внимание!!\nУдаление операционной схемы приведет к удалению всех операций и собственных концептуальных схем.\nДанное действие нельзя отменить.\nВы уверены, что хотите удалить данную ОСС?',
|
||||||
generateWordforms: 'Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?',
|
generateWordforms: 'Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?',
|
||||||
restoreArchive: 'При восстановлении архивной версии актуальная схему будет заменена. Продолжить?',
|
restoreArchive: 'При восстановлении архивной версии актуальная схему будет заменена. Продолжить?',
|
||||||
ownerChange:
|
ownerChange:
|
||||||
|
|
|
@ -169,3 +169,34 @@ export function extractErrorMessage(error: Error | AxiosError): string {
|
||||||
}
|
}
|
||||||
return error.message;
|
return error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert array of objects to CSV Blob.
|
||||||
|
*/
|
||||||
|
export function convertToCSV(targetObj: object[]): Blob {
|
||||||
|
if (!targetObj || targetObj.length === 0) {
|
||||||
|
return new Blob([], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
}
|
||||||
|
const separator = ',';
|
||||||
|
const keys = Object.keys(targetObj[0]);
|
||||||
|
|
||||||
|
const csvContent =
|
||||||
|
keys.join(separator) +
|
||||||
|
'\n' +
|
||||||
|
(targetObj as Record<string, string | Date | number>[])
|
||||||
|
.map(item => {
|
||||||
|
return keys
|
||||||
|
.map(k => {
|
||||||
|
let cell = item[k] === null || item[k] === undefined ? '' : item[k];
|
||||||
|
cell = cell instanceof Date ? cell.toLocaleString() : cell.toString().replace(/"/g, '""');
|
||||||
|
if (cell.search(/("|,|\n)/g) >= 0) {
|
||||||
|
cell = `"${cell}"`;
|
||||||
|
}
|
||||||
|
return cell;
|
||||||
|
})
|
||||||
|
.join(separator);
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user