Compare commits

..

8 Commits

Author SHA1 Message Date
Ivan
566c6be91d M: Improve OSS -> RSForm navigation and fix term graph
Some checks failed
Frontend CI / build (22.x) (push) Waiting to run
Backend CI / build (3.12) (push) Has been cancelled
2024-08-19 19:53:51 +03:00
Ivan
0c10162718 F: Enable tailwind universal styles optimization 2024-08-19 19:32:40 +03:00
Ivan
a6742a7b7c M: Improve typification and expression visibility 2024-08-19 19:15:21 +03:00
Ivan
02afd44488 M: Minor UI fixes and coverage fix 2024-08-19 18:32:21 +03:00
Ivan
13442c44aa B: Fix convention editing for OSS 2024-08-19 17:05:15 +03:00
Ivan
5cc7ee1353 B: Fix substitution editor 2024-08-19 12:32:52 +03:00
Ivan
f8f1ba4a62 M: Improve label visibility 2024-08-19 12:05:46 +03:00
Ivan
c6e64fddb8 M: Minor UI improvements 2024-08-19 10:55:52 +03:00
26 changed files with 101 additions and 64 deletions

View File

@ -557,8 +557,7 @@ class OperationSchema:
if old_data['term_forms'] == cst.term_forms:
new_data['term_forms'] = data['term_forms']
if 'convention' in data:
if old_data['convention'] == cst.convention:
new_data['convention'] = data['convention']
new_data['convention'] = data['convention']
if 'definition_formal' in data:
new_data['definition_formal'] = replace_globals(data['definition_formal'], mapping)
if 'term_raw' in data:

View File

@ -92,7 +92,8 @@ class TestChangeConstituents(EndpointTester):
'item_data': {
'term_raw': 'Test1',
'definition_formal': r'X4\X4',
'definition_raw': '@{X5|sing,datv}'
'definition_raw': '@{X5|sing,datv}',
'convention': 'test'
}
}
response = self.executeOK(data=data, schema=self.ks1.model.pk)
@ -102,8 +103,10 @@ class TestChangeConstituents(EndpointTester):
self.assertEqual(self.ks1X1.term_raw, data['item_data']['term_raw'])
self.assertEqual(self.ks1X1.definition_formal, data['item_data']['definition_formal'])
self.assertEqual(self.ks1X1.definition_raw, data['item_data']['definition_raw'])
self.assertEqual(self.ks1X1.convention, data['item_data']['convention'])
self.assertEqual(d2.definition_resolved, data['item_data']['term_raw'])
self.assertEqual(inherited_cst.term_raw, data['item_data']['term_raw'])
self.assertEqual(inherited_cst.convention, data['item_data']['convention'])
self.assertEqual(inherited_cst.definition_formal, r'X1\X1')
self.assertEqual(inherited_cst.definition_raw, r'@{X2|sing,datv}')

View File

@ -241,7 +241,11 @@ class RSForm:
old_data = {}
term_changed = False
if 'convention' in data:
cst.convention = data['convention']
if cst.convention == data['convention']:
del data['convention']
else:
old_data['convention'] = cst.convention
cst.convention = data['convention']
if 'definition_formal' in data:
if cst.definition_formal == data['definition_formal']:
del data['definition_formal']

View File

@ -23,14 +23,14 @@ function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaPro
{data.term_resolved || data.term_raw}
</p>
) : null}
<p>
<p className='break-all'>
<b>Типизация: </b>
{labelCstTypification(data)}
<span className='font-math'>{labelCstTypification(data)}</span>
</p>
{data.definition_formal ? (
<p>
<b>Выражение: </b>
{data.definition_formal}
<span className='font-math'>{data.definition_formal}</span>
</p>
) : null}
{data.definition_resolved ? (

View File

@ -159,7 +159,7 @@ function PickSubstitutions({
id: 'right_alias',
size: 65,
cell: props => (
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_1_`} />
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_2_`} />
)
}),
columnHelper.accessor(item => item.original_source.alias, {

View File

@ -1,7 +1,7 @@
import clsx from 'clsx';
import { globals } from '@/utils/constants';
import { truncateText } from '@/utils/utils';
import { truncateToLastWord } from '@/utils/utils';
import { CProps } from '../props';
@ -11,7 +11,7 @@ export interface TextContentProps extends CProps.Styling {
}
function TextContent({ className, text, maxLength, ...restProps }: TextContentProps) {
const truncated = maxLength ? truncateText(text, maxLength) : text;
const truncated = maxLength ? truncateToLastWord(text, maxLength) : text;
const isTruncated = maxLength && text.length > maxLength;
return (
<div

View File

@ -14,7 +14,6 @@ import { useLibrary } from '@/context/LibraryContext';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
import { IOperationSchema } from '@/models/oss';
import { sortItemsForOSS } from '@/models/ossAPI';
import { limits, patterns } from '@/utils/constants';
interface TabInputOperationProps {
oss: IOperationSchema;
@ -67,8 +66,6 @@ function TabInputOperation({
id='operation_alias'
label='Сокращение'
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
value={alias}
onChange={event => setAlias(event.target.value)}
disabled={attachedID !== undefined}

View File

@ -4,7 +4,6 @@ import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { IOperationSchema, OperationID } from '@/models/oss';
import { limits, patterns } from '@/utils/constants';
import PickMultiOperation from '../../components/select/PickMultiOperation';
@ -44,8 +43,6 @@ function TabSynthesisOperation({
id='operation_alias'
label='Сокращение'
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
value={alias}
onChange={event => setAlias(event.target.value)}
/>

View File

@ -45,6 +45,7 @@ function DlgDeleteOperation({ hideWindow, target, onSubmit }: DlgDeleteOperation
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
value={keepConstituents}
setValue={setKeepConstituents}
disabled={target.result === null}
/>
<Checkbox
label='Удалить схему'
@ -55,7 +56,7 @@ function DlgDeleteOperation({ hideWindow, target, onSubmit }: DlgDeleteOperation
}
value={deleteSchema}
setValue={setDeleteSchema}
disabled={!target.is_owned || target.result === undefined}
disabled={!target.is_owned || target.result === null}
/>
</Modal>
);

View File

@ -67,8 +67,8 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
if (cache.loading || schemas.length !== schemasIDs.length) {
return;
}
setSubstitutions(() =>
target.substitutions.filter(sub => {
setSubstitutions(prev =>
prev.filter(sub => {
const original = cache.getSchemaByCst(sub.original);
if (!original || !schemasIDs.includes(original.id)) {
return false;
@ -80,7 +80,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
return true;
})
);
}, [schemasIDs, schemas, cache.loading, target.substitutions]);
}, [schemasIDs, schemas, cache.loading]);
const handleSubmit = () => {
const data: IOperationUpdateData = {

View File

@ -1,7 +1,6 @@
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { limits, patterns } from '@/utils/constants';
interface TabOperationProps {
alias: string;
@ -26,8 +25,6 @@ function TabOperation({ alias, setAlias, title, setTitle, comment, setComment }:
id='operation_alias'
label='Сокращение'
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
value={alias}
onChange={event => setAlias(event.target.value)}
/>

View File

@ -26,7 +26,7 @@ import useLocalStorage from '@/hooks/useLocalStorage';
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { ILibraryCreateData } from '@/models/library';
import { combineLocation, validateLocation } from '@/models/libraryAPI';
import { EXTEOR_TRS_FILE, limits, patterns, storage } from '@/utils/constants';
import { EXTEOR_TRS_FILE, storage } from '@/utils/constants';
import { information } from '@/utils/labels';
function FormCreateItem() {
@ -153,8 +153,6 @@ function FormCreateItem() {
label='Сокращение'
placeholder={file && 'Загрузить из файла'}
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
value={alias}
onChange={event => setAlias(event.target.value)}
/>

View File

@ -11,7 +11,6 @@ import TextInput from '@/components/ui/TextInput';
import { useOSS } from '@/context/OssContext';
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
import ToolbarItemAccess from '@/pages/RSFormPage/EditorRSFormCard/ToolbarItemAccess';
import { limits, patterns } from '@/utils/constants';
import { information } from '@/utils/labels';
import { useOssEdit } from '../OssEditContext';
@ -102,8 +101,6 @@ function FormOSS({ id, isModified, setIsModified }: FormOSSProps) {
required
label='Сокращение'
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
disabled={!controller.isMutable}
value={alias}
onChange={event => setAlias(event.target.value)}

View File

@ -5,13 +5,17 @@ import TooltipOperation from '@/components/info/TooltipOperation';
import MiniButton from '@/components/ui/MiniButton.tsx';
import Overlay from '@/components/ui/Overlay';
import { OssNodeInternal } from '@/models/miscellaneous';
import { prefixes } from '@/utils/constants';
import { PARAMETER, prefixes } from '@/utils/constants';
import { truncateToLastWord } from '@/utils/utils';
import { useOssEdit } from '../OssEditContext';
function InputNode(node: OssNodeInternal) {
const controller = useOssEdit();
const hasFile = !!node.data.operation.result;
const longLabel = node.data.label.length > PARAMETER.ossLongLabel;
const labelText = truncateToLastWord(node.data.label, PARAMETER.ossTruncateLabel);
const handleOpenSchema = () => {
controller.openOperationSchema(Number(node.id));
@ -21,7 +25,7 @@ function InputNode(node: OssNodeInternal) {
<>
<Handle type='source' position={Position.Bottom} />
<Overlay position='top-0 right-0' className='flex'>
<Overlay position='top-0 right-0' className='flex p-[0.1rem]'>
<MiniButton
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
noHover
@ -41,8 +45,18 @@ function InputNode(node: OssNodeInternal) {
</Overlay>
) : null}
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
{node.data.label}
<div id={`${prefixes.operation_list}${node.id}`} className='h-[34px] w-[144px] flex items-center justify-center'>
<div
className='text-center'
style={{
fontSize: longLabel ? '12px' : '14px',
lineHeight: longLabel ? '16px' : '20px',
paddingLeft: '4px',
paddingRight: longLabel ? '10px' : '4px'
}}
>
{labelText}
</div>
{controller.showTooltip && !node.dragging ? (
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
) : null}

View File

@ -1,3 +1,5 @@
'use client';
import { Handle, Position } from 'reactflow';
import { IconConsolidation, IconRSForm } from '@/components/Icons';
@ -5,7 +7,8 @@ import TooltipOperation from '@/components/info/TooltipOperation';
import MiniButton from '@/components/ui/MiniButton.tsx';
import Overlay from '@/components/ui/Overlay';
import { OssNodeInternal } from '@/models/miscellaneous';
import { prefixes } from '@/utils/constants';
import { PARAMETER, prefixes } from '@/utils/constants';
import { truncateToLastWord } from '@/utils/utils';
import { useOssEdit } from '../OssEditContext';
@ -13,6 +16,8 @@ function OperationNode(node: OssNodeInternal) {
const controller = useOssEdit();
const hasFile = !!node.data.operation.result;
const longLabel = node.data.label.length > PARAMETER.ossLongLabel;
const labelText = truncateToLastWord(node.data.label, PARAMETER.ossTruncateLabel);
const handleOpenSchema = () => {
controller.openOperationSchema(Number(node.id));
@ -22,14 +27,9 @@ function OperationNode(node: OssNodeInternal) {
<>
<Handle type='source' position={Position.Bottom} />
<Overlay position='top-0 right-0' className='flex flex-col gap-1'>
<Overlay position='top-0 right-0' className='flex flex-col gap-1 p-[0.1rem]'>
<MiniButton
icon={
<IconRSForm
className={hasFile ? 'clr-text-green' : 'clr-text-red'}
size={node.data.operation.is_consolidation ? '0.6rem' : '0.75rem'}
/>
}
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
noHover
noPadding
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
@ -39,7 +39,7 @@ function OperationNode(node: OssNodeInternal) {
/>
{node.data.operation.is_consolidation ? (
<MiniButton
icon={<IconConsolidation className='clr-text-primary' size='0.6rem' />}
icon={<IconConsolidation className='clr-text-primary' size='0.75rem' />}
disabled
noPadding
noHover
@ -55,8 +55,18 @@ function OperationNode(node: OssNodeInternal) {
</Overlay>
) : null}
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
{node.data.label}
<div id={`${prefixes.operation_list}${node.id}`} className='h-[34px] w-[144px] flex items-center justify-center'>
<div
className='px-1 text-center'
style={{
fontSize: longLabel ? '12px' : '14px',
lineHeight: longLabel ? '16px' : '20px',
paddingLeft: '4px',
paddingRight: longLabel ? '10px' : '4px'
}}
>
{labelText}
</div>
{controller.showTooltip && !node.dragging ? (
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
) : null}

View File

@ -335,7 +335,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
}
}
const canvasWidth = useMemo(() => 'calc(100vw - 1rem)', []);
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
const OssNodeTypes: NodeTypes = useMemo(
@ -358,7 +357,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
fitView
nodeTypes={OssNodeTypes}
maxZoom={2}
minZoom={0.75}
minZoom={0.5}
nodesConnectable={false}
snapToGrid={true}
snapGrid={[PARAMETER.ossGridSize, PARAMETER.ossGridSize]}
@ -413,7 +412,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
{...menuProps}
/>
) : null}
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
<div className='relative w-[100vw]' style={{ height: canvasHeight }}>
{graph}
</div>
</AnimateFade>

View File

@ -33,6 +33,8 @@ import { UserID, UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants';
import { errors, information } from '@/utils/labels';
import { RSTabID } from '../RSFormPage/RSTabs';
export interface ICreateOperationPrompt {
x: number;
y: number;
@ -206,7 +208,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
if (!node?.result) {
return;
}
router.push(urls.schema(node.result));
router.push(urls.schema_props({ id: node.result, tab: RSTabID.CST_LIST }));
},
[router, model]
);

View File

@ -220,7 +220,7 @@ function FormConstituenta({
label={isBasic ? 'Конвенция' : 'Комментарий'}
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
value={convention}
disabled={disabled}
disabled={disabled || (isBasic && state?.is_inherited)}
rows={convention.length > 2 * ROW_SIZE_IN_CHARACTERS || convention.includes('\n') ? 4 : 2}
onChange={event => setConvention(event.target.value)}
/>

View File

@ -10,7 +10,6 @@ import SubmitButton from '@/components/ui/SubmitButton';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
import { limits, patterns } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext';
import ToolbarItemAccess from './ToolbarItemAccess';
@ -102,8 +101,6 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
required
label='Сокращение'
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
disabled={!controller.isContentEditable}
value={alias}
onChange={event => setAlias(event.target.value)}

View File

@ -12,8 +12,9 @@ import TextURL from '@/components/ui/TextURL';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import useWindowSize from '@/hooks/useWindowSize';
import { ConstituentaID, IConstituenta } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
import { PARAMETER, prefixes } from '@/utils/constants';
import { labelCstTypification } from '@/utils/labels';
import { truncateToSymbol } from '@/utils/utils';
interface TableRSListProps {
items?: IConstituenta[];
@ -90,8 +91,13 @@ function TableRSList({
id: 'type',
header: 'Типизация',
enableHiding: true,
size: 150,
minSize: 150,
maxSize: 200,
cell: props => (
<div className={clsx('min-w-[9.3rem] max-w-[9.3rem]', 'text-sm break-words')}>{props.getValue()}</div>
<div className={clsx('min-w-[9.3rem] max-w-[9.3rem]', 'text-xs break-words')}>
{truncateToSymbol(props.getValue(), PARAMETER.typificationTruncate)}
</div>
)
}),
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {

View File

@ -99,8 +99,7 @@ function TermGraph({
);
useLayoutEffect(() => {
graphRef.current?.resetControls(true);
graphRef.current?.centerGraph();
graphRef.current?.fitNodesInView([], { animated: true });
}, [toggleResetView, graphRef]);
useLayoutEffect(() => {

View File

@ -66,7 +66,7 @@
border: 1px solid;
padding: 2px;
width: 150px;
height: 30px;
height: 40px;
font-size: 14px;
border-radius: 5px;

View File

@ -25,6 +25,11 @@ export const PARAMETER = {
graphPopupDelay: 500, // milliseconds delay for graph popup selections
graphRefreshDelay: 10, // milliseconds delay for graph viewpoint reset
typificationTruncate: 42, // characters - threshold for long typification - truncate
ossLongLabel: 14, // characters - threshold for long labels - small font
ossTruncateLabel: 28, // characters - threshold for long labels - truncate
logicLabel: 'LOGIC',
exteorVersion: '4.9.3',
@ -35,7 +40,6 @@ export const PARAMETER = {
* Numeric limitations.
*/
export const limits = {
library_alias_len: 12,
location_len: 500
};
@ -48,8 +52,7 @@ export const EXTEOR_TRS_FILE = '.trs';
* Regex patterns for data validation.
*/
export const patterns = {
login: '^[a-zA-Z][a-zA-Z0-9_\\-]{1,}[a-zA-Z0-9]$',
library_alias: `.{1,${limits.library_alias_len}}`
login: '^[a-zA-Z][a-zA-Z0-9_\\-]{1,}[a-zA-Z0-9]$'
};
/**

View File

@ -67,9 +67,9 @@ export function applyPattern(text: string, mapping: Record<string, string>, patt
}
/**
* Truncate text to max symbols. Add ellipsis if truncated.
* Truncate text to last word up to max symbols. Add ellipsis if truncated.
*/
export function truncateText(text: string, maxSymbols: number): string {
export function truncateToLastWord(text: string, maxSymbols: number): string {
if (text.length <= maxSymbols) {
return text;
}
@ -81,6 +81,17 @@ export function truncateText(text: string, maxSymbols: number): string {
return trimmedText.slice(0, lastSpaceIndex) + '...';
}
/**
* Truncate text to max symbols. Add ellipsis if truncated.
*/
export function truncateToSymbol(text: string, maxSymbols: number): string {
if (text.length <= maxSymbols) {
return text;
}
const trimmedText = text.slice(0, maxSymbols);
return trimmedText + '...';
}
/**
* Check if Axios response is html.
*/

View File

@ -16,5 +16,8 @@ export default {
},
extend: {}
},
plugins: []
plugins: [],
experimental: {
optimizeUniversalDefaults: true
}
};

View File

@ -11,7 +11,7 @@ function BackendCoverage() {
$coverageExec = "$backend\venv\Scripts\coverage.exe"
$djangoSrc = "$backend\manage.py"
$exclude = '*/venv/*,*/tests/*,*/migrations/*,*__init__.py,manage.py,apps.py,urls.py,settings.py'
$exclude = '*/venv/*,*/tests/*,*/migrations/*,*__init__.py,shared/*,manage.py,apps.py,urls.py,settings.py,admin.py'
& $coverageExec run --omit=$exclude $djangoSrc test
& $coverageExec report