F: Wire crucial attribute for frontend

This commit is contained in:
Ivan 2025-07-29 15:28:30 +03:00
parent f78bda5057
commit e3b20551d5
12 changed files with 90 additions and 43 deletions

View File

@ -84,15 +84,18 @@ class TestVersionViews(EndpointTester):
alias='A1',
cst_type='axiom',
definition_formal='X1=X1',
order=1
order=1,
crucial=True
)
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
a1.definition_formal = 'X1=X2'
a1.crucial = False
a1.save()
response = self.executeOK(schema=self.owned_id, version=version_id)
loaded_a1 = response.data['items'][1]
self.assertEqual(loaded_a1['definition_formal'], 'X1=X1')
self.assertEqual(loaded_a1['crucial'], True)
self.assertEqual(loaded_a1['parse']['status'], 'verified')

View File

@ -0,0 +1,37 @@
import { globalIDs } from '@/utils/constants';
import { type Button as ButtonStyle } from '../props';
import { cn } from '../utils';
interface TextButtonProps extends ButtonStyle {
/** Text to display second. */
text: string;
}
/**
* Customizable `button` with text, transparent background and no additional styling.
*/
export function TextButton({ text, title, titleHtml, hideTitle, className, ...restProps }: TextButtonProps) {
return (
<button
tabIndex={-1}
type='button'
className={cn(
'self-start cc-label cc-hover-underline',
'font-medium text-primary select-none disabled:text-foreground',
'cursor-pointer disabled:cursor-default',
'outline-hidden',
'select-text',
className
)}
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
data-tooltip-html={titleHtml}
data-tooltip-content={title}
data-tooltip-hidden={hideTitle}
aria-label={!text ? title : undefined}
{...restProps}
>
{text}
</button>
);
}

View File

@ -30,7 +30,7 @@ export function TextURL({ text, href, title, color = 'text-primary', onClick }:
);
} else if (onClick) {
return (
<button type='button' tabIndex={-1} className={design} onClick={onClick}>
<button type='button' tabIndex={-1} className={design} title={title} onClick={onClick}>
{text}
</button>
);

View File

@ -2,7 +2,6 @@ import {
IconChild,
IconClone,
IconDestroy,
IconEdit,
IconFilter,
IconGraphSelection,
IconKeyboard,
@ -114,8 +113,7 @@ export function HelpRSEditor() {
<h2>Термин и Текстовое определение</h2>
<ul>
<li>
<IconEdit className='inline-icon' /> редактирование{' '}
<LinkTopic text='Имени' topic={HelpTopic.CC_CONSTITUENTA} /> /{' '}
<kbd>Клик</kbd> редактирование <LinkTopic text='Имени' topic={HelpTopic.CC_CONSTITUENTA} /> /{' '}
<LinkTopic text='Термина' topic={HelpTopic.CC_CONSTITUENTA} />
</li>
<li>

View File

@ -77,6 +77,7 @@ export function ToolbarSchema({
const targetType = activeCst?.cst_type ?? CstType.BASE;
const data: ICreateConstituentaDTO = {
insert_after: activeCst?.id ?? null,
crucial: false,
cst_type: targetType,
alias: generateAlias(targetType, schema),
term_raw: '',
@ -96,6 +97,7 @@ export function ToolbarSchema({
itemID: schema.id,
data: {
insert_after: activeCst.id,
crucial: activeCst.crucial,
cst_type: activeCst.cst_type,
alias: generateAlias(activeCst.cst_type, schema),
term_raw: activeCst.term_raw,

View File

@ -276,6 +276,7 @@ export const schemaConstituentaBasics = z.strictObject({
id: z.coerce.number(),
alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(),
crucial: z.boolean(),
cst_type: schemaCstType,
definition_formal: z.string(),
definition_raw: z.string(),
@ -321,7 +322,8 @@ export const schemaVersionCreatedResponse = z.strictObject({
export const schemaCreateConstituenta = schemaConstituentaBasics
.pick({
cst_type: true,
term_forms: true
term_forms: true,
crucial: true
})
.extend({
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
@ -342,6 +344,7 @@ export const schemaUpdateConstituenta = z.strictObject({
item_data: z.strictObject({
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField).optional(),
cst_type: schemaCstType.optional(),
crucial: z.boolean().optional(),
convention: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
definition_formal: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
definition_raw: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),

View File

@ -47,6 +47,7 @@ export interface TermForm {
/** Represents Constituenta. */
export interface IConstituenta {
id: number;
crucial: boolean;
alias: string;
convention: string;
cst_type: CstType;

View File

@ -107,7 +107,7 @@ export function EditorConstituenta() {
isNarrow={isNarrow}
/>
<div className='mx-0 min-w-140 md:mx-auto pt-8 md:w-195 shrink-0 xs:pt-0'>
<div className='mx-0 min-w-120 md:mx-auto pt-8 md:w-195 shrink-0 xs:pt-0'>
{activeCst ? (
<FormConstituenta
key={activeCst.id}

View File

@ -6,8 +6,9 @@ import { Controller, useForm } from 'react-hook-form';
import { toast } from 'react-toastify';
import { zodResolver } from '@hookform/resolvers/zod';
import { MiniButton, SubmitButton } from '@/components/control';
import { IconChild, IconEdit, IconPredecessor, IconSave } from '@/components/icons';
import { SubmitButton } from '@/components/control';
import { TextButton } from '@/components/control/text-button';
import { IconChild, IconPredecessor, IconSave } from '@/components/icons';
import { TextArea } from '@/components/input';
import { Indicator } from '@/components/view';
import { useDialogsStore } from '@/stores/dialogs';
@ -159,32 +160,30 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
}
return (
<form id={id} className='relative cc-column mt-1 px-6 py-1' onSubmit={event => void handleSubmit(onSubmit)(event)}>
{!disabled || isProcessing ? (
<MiniButton
title={isModified ? tooltipText.unsaved : 'Редактировать словоформы термина'}
aria-label='Редактировать словоформы термина'
<form
id={id}
className='relative cc-column mt-1 px-6 pb-1 pt-8'
onSubmit={event => void handleSubmit(onSubmit)(event)}
>
<div className='absolute z-pop top-0 left-6 flex select-text font-medium whitespace-nowrap pt-1'>
<TextButton
text='Термин' //
title={disabled ? undefined : isModified ? tooltipText.unsaved : 'Редактировать словоформы термина'}
onClick={handleEditTermForms}
className='absolute z-pop top-0 left-[calc(7ch+4px)]'
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
disabled={isModified || disabled}
/>
) : null}
<div className='absolute z-pop top-0 left-[calc(7ch+4px+3rem)] flex select-none'>
<div className='pt-1 text-sm font-medium min-w-16 whitespace-nowrap select-text cursor-default'>
<span>Имя </span>
<span className='ml-1'>{activeCst?.alias ?? ''}</span>
<TextButton
text='Имя' //
className='ml-6'
title={disabled ? undefined : isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
onClick={handleRenameCst}
disabled={isModified || disabled}
/>
<div className='ml-2 text-sm font-medium min-w-16 whitespace-nowrap select-text cursor-default'>
{activeCst?.alias ?? ''}
</div>
{!disabled || isProcessing ? (
<MiniButton
title={isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
aria-label='Переименовать конституенту'
onClick={handleRenameCst}
icon={<IconEdit size='1rem' className='icon-primary' />}
disabled={isModified}
/>
) : null}
</div>
<Controller
@ -193,7 +192,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
render={({ field }) => (
<RefsInput
id='cst_term'
label='Термин'
aria-label='Термин'
maxHeight='8rem'
placeholder={disabled ? '' : 'Обозначение для текстовых определений'}
schema={schema}
@ -285,14 +284,10 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
) : null}
{!showConvention && (!disabled || isProcessing) ? (
<button
type='button'
tabIndex={-1}
className='self-start cc-label text-primary hover:underline select-none'
<TextButton
text='Добавить комментарий' //
onClick={() => setForceComment(true)}
>
Добавить комментарий
</button>
/>
) : null}
{!disabled || isProcessing ? (

View File

@ -41,11 +41,11 @@ export function StatusBar({ className, isModified, processing, activeCst, parseD
})();
return (
<div className={cn('pl-34 xs:pl-8 flex gap-1', className)}>
<div className={cn('pl-22 xs:pl-8 flex gap-1', className)}>
<div
tabIndex={0}
className={clsx(
'w-40 h-7',
'w-32 h-7',
'px-2 flex items-center justify-center',
'border',
'select-none',
@ -64,9 +64,9 @@ export function StatusBar({ className, isModified, processing, activeCst, parseD
</div>
) : null}
{!processing ? (
<div className='cc-fade-in flex items-center gap-2'>
<div className='cc-fade-in flex items-center gap-1'>
<IconExpressionStatus size='1rem' value={status} />
<span className='pb-0.5 font-controls pr-2'>{labelExpressionStatus(status)}</span>
<span className='font-controls pr-1 text-sm'>{labelExpressionStatus(status)}</span>
</div>
) : null}
</div>

View File

@ -225,6 +225,7 @@ export const RSEditState = ({
definition_formal: definition ?? '',
definition_raw: '',
convention: '',
crucial: false,
term_forms: []
};
if (skipDialog) {
@ -248,6 +249,7 @@ export const RSEditState = ({
definition_formal: activeCst.definition_formal,
definition_raw: activeCst.definition_raw,
convention: activeCst.convention,
crucial: activeCst.crucial,
term_forms: activeCst.term_forms
}
}).then(onCreateCst);

View File

@ -40,6 +40,12 @@
}
}
@utility cc-hover-underline {
&:hover:not(:disabled) {
text-decoration: underline;
}
}
@utility focus-outline {
--focus-color: var(--color-ring);