Compare commits
9 Commits
d8286e6339
...
25029a212b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
25029a212b | ||
![]() |
88652d57d3 | ||
![]() |
7790cc4ef2 | ||
![]() |
51ad937b99 | ||
![]() |
a7ad9b86f5 | ||
![]() |
800e492d89 | ||
![]() |
7555b2219d | ||
![]() |
0a5fb5eecf | ||
![]() |
49ff1c8f6c |
|
@ -83,7 +83,6 @@ export { LuNewspaper as IconDefinition } from 'react-icons/lu';
|
||||||
export { LuDna as IconTerminology } from 'react-icons/lu';
|
export { LuDna as IconTerminology } from 'react-icons/lu';
|
||||||
export { FaRegHandshake as IconConvention } from 'react-icons/fa6';
|
export { FaRegHandshake as IconConvention } from 'react-icons/fa6';
|
||||||
export { LiaCloneSolid as IconChild } from 'react-icons/lia';
|
export { LiaCloneSolid as IconChild } from 'react-icons/lia';
|
||||||
export { RiParentLine as IconParent } from 'react-icons/ri';
|
|
||||||
export { TbTopologyRing as IconConsolidation } from 'react-icons/tb';
|
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';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
|
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
|
||||||
import SelectConstituenta from '@/components/select/SelectConstituenta';
|
import SelectConstituenta from '@/components/select/SelectConstituenta';
|
||||||
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
|
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { ILibraryItem } from '@/models/library';
|
import { ILibraryItem } from '@/models/library';
|
||||||
|
@ -13,13 +13,14 @@ import { ICstSubstitute, IMultiSubstitution } from '@/models/oss';
|
||||||
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
||||||
import { errors } from '@/utils/labels';
|
import { errors } from '@/utils/labels';
|
||||||
|
|
||||||
import { IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
|
import { IconAccept, IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
|
||||||
import NoData from '../ui/NoData';
|
import NoData from '../ui/NoData';
|
||||||
import SelectLibraryItem from './SelectLibraryItem';
|
import SelectLibraryItem from './SelectLibraryItem';
|
||||||
|
|
||||||
interface PickSubstitutionsProps {
|
interface PickSubstitutionsProps {
|
||||||
substitutions: ICstSubstitute[];
|
substitutions: ICstSubstitute[];
|
||||||
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||||
|
suggestions?: ICstSubstitute[];
|
||||||
|
|
||||||
prefixID: string;
|
prefixID: string;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
|
@ -34,6 +35,7 @@ const columnHelper = createColumnHelper<IMultiSubstitution>();
|
||||||
function PickSubstitutions({
|
function PickSubstitutions({
|
||||||
substitutions,
|
substitutions,
|
||||||
setSubstitutions,
|
setSubstitutions,
|
||||||
|
suggestions,
|
||||||
prefixID,
|
prefixID,
|
||||||
rows,
|
rows,
|
||||||
schemas,
|
schemas,
|
||||||
|
@ -55,6 +57,15 @@ function PickSubstitutions({
|
||||||
const [deleteRight, setDeleteRight] = useState(true);
|
const [deleteRight, setDeleteRight] = useState(true);
|
||||||
const toggleDelete = () => setDeleteRight(prev => !prev);
|
const toggleDelete = () => setDeleteRight(prev => !prev);
|
||||||
|
|
||||||
|
const [ignores, setIgnores] = useState<ICstSubstitute[]>([]);
|
||||||
|
const filteredSuggestions = useMemo(
|
||||||
|
() =>
|
||||||
|
suggestions?.filter(
|
||||||
|
item => !ignores.find(ignore => ignore.original === item.original && ignore.substitution === item.substitution)
|
||||||
|
) ?? [],
|
||||||
|
[ignores, suggestions]
|
||||||
|
);
|
||||||
|
|
||||||
const getSchemaByCst = useCallback(
|
const getSchemaByCst = useCallback(
|
||||||
(id: ConstituentaID): IRSForm | undefined => {
|
(id: ConstituentaID): IRSForm | undefined => {
|
||||||
for (const schema of schemas) {
|
for (const schema of schemas) {
|
||||||
|
@ -82,14 +93,23 @@ function PickSubstitutions({
|
||||||
);
|
);
|
||||||
|
|
||||||
const substitutionData: IMultiSubstitution[] = useMemo(
|
const substitutionData: IMultiSubstitution[] = useMemo(
|
||||||
() =>
|
() => [
|
||||||
substitutions.map(item => ({
|
...substitutions.map(item => ({
|
||||||
original_source: getSchemaByCst(item.original)!,
|
original_source: getSchemaByCst(item.original)!,
|
||||||
original: getConstituenta(item.original)!,
|
original: getConstituenta(item.original)!,
|
||||||
substitution: getConstituenta(item.substitution)!,
|
substitution: getConstituenta(item.substitution)!,
|
||||||
substitution_source: getSchemaByCst(item.substitution)!
|
substitution_source: getSchemaByCst(item.substitution)!,
|
||||||
|
is_suggestion: false
|
||||||
})),
|
})),
|
||||||
[getConstituenta, getSchemaByCst, substitutions]
|
...filteredSuggestions.map(item => ({
|
||||||
|
original_source: getSchemaByCst(item.original)!,
|
||||||
|
original: getConstituenta(item.original)!,
|
||||||
|
substitution: getConstituenta(item.substitution)!,
|
||||||
|
substitution_source: getSchemaByCst(item.substitution)!,
|
||||||
|
is_suggestion: true
|
||||||
|
}))
|
||||||
|
],
|
||||||
|
[getConstituenta, getSchemaByCst, substitutions, filteredSuggestions]
|
||||||
);
|
);
|
||||||
|
|
||||||
function addSubstitution() {
|
function addSubstitution() {
|
||||||
|
@ -121,19 +141,34 @@ function PickSubstitutions({
|
||||||
setRightCst(undefined);
|
setRightCst(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteRow = useCallback(
|
const handleDeclineSuggestion = useCallback(
|
||||||
(row: number) => {
|
(item: IMultiSubstitution) => {
|
||||||
|
setIgnores(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||||
|
},
|
||||||
|
[setIgnores]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAcceptSuggestion = useCallback(
|
||||||
|
(item: IMultiSubstitution) => {
|
||||||
|
setSubstitutions(prev => [...prev, { original: item.original.id, substitution: item.substitution.id }]);
|
||||||
|
},
|
||||||
|
[setSubstitutions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteSubstitution = useCallback(
|
||||||
|
(target: IMultiSubstitution) => {
|
||||||
|
handleDeclineSuggestion(target);
|
||||||
setSubstitutions(prev => {
|
setSubstitutions(prev => {
|
||||||
const newItems: ICstSubstitute[] = [];
|
const newItems: ICstSubstitute[] = [];
|
||||||
prev.forEach((item, index) => {
|
prev.forEach(item => {
|
||||||
if (index !== row) {
|
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
|
||||||
newItems.push(item);
|
newItems.push(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return newItems;
|
return newItems;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setSubstitutions]
|
[setSubstitutions, handleDeclineSuggestion]
|
||||||
);
|
);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
|
@ -169,19 +204,47 @@ function PickSubstitutions({
|
||||||
}),
|
}),
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
cell: props => (
|
cell: props =>
|
||||||
<div className='max-w-fit'>
|
props.row.original.is_suggestion ? (
|
||||||
<MiniButton
|
<div className='max-w-fit'>
|
||||||
noHover
|
<MiniButton
|
||||||
title='Удалить'
|
noHover
|
||||||
icon={<IconRemove size='1rem' className='icon-red' />}
|
title='Принять предложение'
|
||||||
onClick={() => handleDeleteRow(props.row.index)}
|
icon={<IconAccept size='1rem' className='icon-green' />}
|
||||||
/>
|
onClick={() => handleAcceptSuggestion(props.row.original)}
|
||||||
</div>
|
/>
|
||||||
)
|
<MiniButton
|
||||||
|
noHover
|
||||||
|
title='Игнорировать предложение'
|
||||||
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||||
|
onClick={() => handleDeclineSuggestion(props.row.original)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='max-w-fit'>
|
||||||
|
<MiniButton
|
||||||
|
noHover
|
||||||
|
title='Удалить'
|
||||||
|
icon={<IconRemove size='1rem' className='icon-red' />}
|
||||||
|
onClick={() => handleDeleteSubstitution(props.row.original)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
[handleDeleteRow, colors, prefixID]
|
[handleDeleteSubstitution, handleDeclineSuggestion, handleAcceptSuggestion, colors, prefixID]
|
||||||
|
);
|
||||||
|
|
||||||
|
const conditionalRowStyles = useMemo(
|
||||||
|
(): IConditionalStyle<IMultiSubstitution>[] => [
|
||||||
|
{
|
||||||
|
when: (item: IMultiSubstitution) => item.is_suggestion,
|
||||||
|
style: {
|
||||||
|
backgroundColor: colors.bgOrange50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[colors]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -265,6 +328,7 @@ function PickSubstitutions({
|
||||||
<p>Добавьте отождествление</p>
|
<p>Добавьте отождествление</p>
|
||||||
</NoData>
|
</NoData>
|
||||||
}
|
}
|
||||||
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Label from './Label';
|
||||||
export interface TextAreaProps extends CProps.Editor, CProps.Colors, CProps.TextArea {
|
export interface TextAreaProps extends CProps.Editor, CProps.Colors, CProps.TextArea {
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
noResize?: boolean;
|
noResize?: boolean;
|
||||||
|
fitContent?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextArea({
|
function TextArea({
|
||||||
|
@ -18,6 +19,7 @@ function TextArea({
|
||||||
noOutline,
|
noOutline,
|
||||||
noResize,
|
noResize,
|
||||||
className,
|
className,
|
||||||
|
fitContent,
|
||||||
colors = 'clr-input',
|
colors = 'clr-input',
|
||||||
...restProps
|
...restProps
|
||||||
}: TextAreaProps) {
|
}: TextAreaProps) {
|
||||||
|
@ -40,6 +42,7 @@ function TextArea({
|
||||||
'leading-tight',
|
'leading-tight',
|
||||||
'overflow-x-hidden overflow-y-auto',
|
'overflow-x-hidden overflow-y-auto',
|
||||||
{
|
{
|
||||||
|
'cc-fit-content': fitContent,
|
||||||
'resize-none': noResize,
|
'resize-none': noResize,
|
||||||
'border': !noBorder,
|
'border': !noBorder,
|
||||||
'flex-grow max-w-full': dense,
|
'flex-grow max-w-full': dense,
|
||||||
|
|
|
@ -134,12 +134,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
model.owner = newOwner;
|
model.owner = newOwner;
|
||||||
library.localUpdateItem(model);
|
library.reloadItems(() => {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, model, library.localUpdateItem]
|
[itemID, model, library.reloadItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setAccessPolicy = useCallback(
|
const setAccessPolicy = useCallback(
|
||||||
|
@ -157,12 +158,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
model.access_policy = newPolicy;
|
model.access_policy = newPolicy;
|
||||||
library.localUpdateItem(model);
|
library.reloadItems(() => {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, model, library.localUpdateItem]
|
[itemID, model, library.reloadItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setLocation = useCallback(
|
const setLocation = useCallback(
|
||||||
|
@ -180,12 +182,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
model.location = newLocation;
|
model.location = newLocation;
|
||||||
library.localUpdateItem(model);
|
library.reloadItems(() => {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, model, library.localUpdateItem]
|
[itemID, model, library.reloadItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
const setEditors = useCallback(
|
const setEditors = useCallback(
|
||||||
|
@ -203,11 +206,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
onError: setProcessingError,
|
onError: setProcessingError,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
model.editors = newEditors;
|
model.editors = newEditors;
|
||||||
if (callback) callback();
|
library.reloadItems(() => {
|
||||||
|
if (callback) callback();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[itemID, model]
|
[itemID, model, library.reloadItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
const savePositions = useCallback(
|
const savePositions = useCallback(
|
||||||
|
|
|
@ -59,14 +59,7 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
|
||||||
onChange={handleSelectLocation}
|
onChange={handleSelectLocation}
|
||||||
className='max-h-[9.2rem]'
|
className='max-h-[9.2rem]'
|
||||||
/>
|
/>
|
||||||
<TextArea
|
<TextArea id='dlg_cst_body' label='Путь' rows={3} value={body} onChange={event => setBody(event.target.value)} />
|
||||||
id='dlg_cst_body'
|
|
||||||
label='Путь'
|
|
||||||
className='w-[23rem]'
|
|
||||||
rows={3}
|
|
||||||
value={body}
|
|
||||||
onChange={event => setBody(event.target.value)}
|
|
||||||
/>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,6 @@ function DlgCloneLibraryItem({ hideWindow, base, initialLocation, selected, tota
|
||||||
<TextArea
|
<TextArea
|
||||||
id='dlg_cst_body'
|
id='dlg_cst_body'
|
||||||
label='Путь'
|
label='Путь'
|
||||||
className='w-[18rem]'
|
|
||||||
rows={3}
|
rows={3}
|
||||||
value={body}
|
value={body}
|
||||||
onChange={event => setBody(event.target.value)}
|
onChange={event => setBody(event.target.value)}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
import TabLabel from '@/components/ui/TabLabel';
|
import TabLabel from '@/components/ui/TabLabel';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
import usePartialUpdate from '@/hooks/usePartialUpdate';
|
import usePartialUpdate from '@/hooks/usePartialUpdate';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
|
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
|
||||||
|
@ -33,8 +34,10 @@ export enum TabID {
|
||||||
}
|
}
|
||||||
|
|
||||||
function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }: DlgConstituentaTemplateProps) {
|
function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }: DlgConstituentaTemplateProps) {
|
||||||
|
const { retrieveTemplate } = useLibrary();
|
||||||
const [activeTab, setActiveTab] = useState(TabID.TEMPLATE);
|
const [activeTab, setActiveTab] = useState(TabID.TEMPLATE);
|
||||||
|
|
||||||
|
const [templateSchema, setTemplateSchema] = useState<IRSForm | undefined>(undefined);
|
||||||
const [template, updateTemplate] = usePartialUpdate<ITemplateState>({});
|
const [template, updateTemplate] = usePartialUpdate<ITemplateState>({});
|
||||||
const [substitutes, updateSubstitutes] = usePartialUpdate<IArgumentsState>({
|
const [substitutes, updateSubstitutes] = usePartialUpdate<IArgumentsState>({
|
||||||
definition: '',
|
definition: '',
|
||||||
|
@ -43,7 +46,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
const [constituenta, updateConstituenta] = usePartialUpdate<ICstCreateData>({
|
const [constituenta, updateConstituenta] = usePartialUpdate<ICstCreateData>({
|
||||||
cst_type: CstType.TERM,
|
cst_type: CstType.TERM,
|
||||||
insert_after: insertAfter ?? null,
|
insert_after: insertAfter ?? null,
|
||||||
alias: '',
|
alias: generateAlias(CstType.TERM, schema),
|
||||||
convention: '',
|
convention: '',
|
||||||
definition_formal: '',
|
definition_formal: '',
|
||||||
definition_raw: '',
|
definition_raw: '',
|
||||||
|
@ -55,8 +58,12 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
const handleSubmit = () => onCreate(constituenta);
|
const handleSubmit = () => onCreate(constituenta);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
updateConstituenta({ alias: generateAlias(constituenta.cst_type, schema) });
|
if (!template.templateID) {
|
||||||
}, [constituenta.cst_type, updateConstituenta, schema]);
|
setTemplateSchema(undefined);
|
||||||
|
} else {
|
||||||
|
retrieveTemplate(template.templateID, setTemplateSchema);
|
||||||
|
}
|
||||||
|
}, [template.templateID, retrieveTemplate]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!template.prototype) {
|
if (!template.prototype) {
|
||||||
|
@ -72,6 +79,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
} else {
|
} else {
|
||||||
updateConstituenta({
|
updateConstituenta({
|
||||||
cst_type: template.prototype.cst_type,
|
cst_type: template.prototype.cst_type,
|
||||||
|
alias: generateAlias(template.prototype.cst_type, schema),
|
||||||
definition_raw: template.prototype.definition_raw,
|
definition_raw: template.prototype.definition_raw,
|
||||||
definition_formal: template.prototype.definition_formal,
|
definition_formal: template.prototype.definition_formal,
|
||||||
term_raw: template.prototype.term_raw
|
term_raw: template.prototype.term_raw
|
||||||
|
@ -85,7 +93,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [template.prototype, updateConstituenta, updateSubstitutes]);
|
}, [template.prototype, updateConstituenta, updateSubstitutes, schema]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (substitutes.arguments.length === 0 || !template.prototype) {
|
if (substitutes.arguments.length === 0 || !template.prototype) {
|
||||||
|
@ -95,12 +103,13 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
const type = inferTemplatedType(template.prototype.cst_type, substitutes.arguments);
|
const type = inferTemplatedType(template.prototype.cst_type, substitutes.arguments);
|
||||||
updateConstituenta({
|
updateConstituenta({
|
||||||
cst_type: type,
|
cst_type: type,
|
||||||
|
alias: generateAlias(type, schema),
|
||||||
definition_formal: definition
|
definition_formal: definition
|
||||||
});
|
});
|
||||||
updateSubstitutes({
|
updateSubstitutes({
|
||||||
definition: definition
|
definition: definition
|
||||||
});
|
});
|
||||||
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes]);
|
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes, schema]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema));
|
setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema));
|
||||||
|
@ -109,10 +118,10 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
const templatePanel = useMemo(
|
const templatePanel = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<TabTemplate state={template} partialUpdate={updateTemplate} />
|
<TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
),
|
),
|
||||||
[template, updateTemplate]
|
[template, templateSchema, updateTemplate]
|
||||||
);
|
);
|
||||||
|
|
||||||
const argumentsPanel = useMemo(
|
const argumentsPanel = useMemo(
|
||||||
|
|
|
@ -216,6 +216,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
|
||||||
|
|
||||||
<RSInput
|
<RSInput
|
||||||
disabled
|
disabled
|
||||||
|
noTooltip
|
||||||
id='result'
|
id='result'
|
||||||
placeholder='Итоговое определение'
|
placeholder='Итоговое определение'
|
||||||
className='mt-[1.2rem]'
|
className='mt-[1.2rem]'
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { useLibrary } from '@/context/LibraryContext';
|
||||||
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '@/models/rsform';
|
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '@/models/rsform';
|
||||||
import { applyFilterCategory } from '@/models/rsformAPI';
|
import { applyFilterCategory } from '@/models/rsformAPI';
|
||||||
import { prefixes } from '@/utils/constants';
|
import { prefixes } from '@/utils/constants';
|
||||||
|
|
||||||
export interface ITemplateState {
|
export interface ITemplateState {
|
||||||
templateID?: number;
|
templateID?: number;
|
||||||
prototype?: IConstituenta;
|
prototype?: IConstituenta;
|
||||||
|
@ -20,11 +21,11 @@ export interface ITemplateState {
|
||||||
interface TabTemplateProps {
|
interface TabTemplateProps {
|
||||||
state: ITemplateState;
|
state: ITemplateState;
|
||||||
partialUpdate: Dispatch<Partial<ITemplateState>>;
|
partialUpdate: Dispatch<Partial<ITemplateState>>;
|
||||||
|
templateSchema?: IRSForm;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabTemplate({ state, partialUpdate }: TabTemplateProps) {
|
function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps) {
|
||||||
const { templates, retrieveTemplate } = useLibrary();
|
const { templates } = useLibrary();
|
||||||
const [templateSchema, setTemplateSchema] = useState<IRSForm | undefined>(undefined);
|
|
||||||
|
|
||||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||||
|
|
||||||
|
@ -65,14 +66,6 @@ function TabTemplate({ state, partialUpdate }: TabTemplateProps) {
|
||||||
}
|
}
|
||||||
}, [templates, state.templateID, partialUpdate]);
|
}, [templates, state.templateID, partialUpdate]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!state.templateID) {
|
|
||||||
setTemplateSchema(undefined);
|
|
||||||
} else {
|
|
||||||
retrieveTemplate(state.templateID, setTemplateSchema);
|
|
||||||
}
|
|
||||||
}, [state.templateID, retrieveTemplate]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!templateSchema) {
|
if (!templateSchema) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import usePartialUpdate from '@/hooks/usePartialUpdate';
|
import usePartialUpdate from '@/hooks/usePartialUpdate';
|
||||||
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
|
import { CstType, ICstCreateData, IRSForm } from '@/models/rsform';
|
||||||
import { generateAlias, validateNewAlias } from '@/models/rsformAPI';
|
import { generateAlias } from '@/models/rsformAPI';
|
||||||
|
|
||||||
import FormCreateCst from './FormCreateCst';
|
import FormCreateCst from './FormCreateCst';
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
|
||||||
initial || {
|
initial || {
|
||||||
cst_type: CstType.BASE,
|
cst_type: CstType.BASE,
|
||||||
insert_after: null,
|
insert_after: null,
|
||||||
alias: '',
|
alias: generateAlias(CstType.BASE, schema),
|
||||||
convention: '',
|
convention: '',
|
||||||
definition_formal: '',
|
definition_formal: '',
|
||||||
definition_raw: '',
|
definition_raw: '',
|
||||||
|
@ -32,14 +32,6 @@ function DlgCreateCst({ hideWindow, initial, schema, onCreate }: DlgCreateCstPro
|
||||||
|
|
||||||
const handleSubmit = () => onCreate(cstData);
|
const handleSubmit = () => onCreate(cstData);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
updateCstData({ alias: generateAlias(cstData.cst_type, schema) });
|
|
||||||
}, [cstData.cst_type, updateCstData, schema]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setValidated(validateNewAlias(cstData.alias, cstData.cst_type, schema));
|
|
||||||
}, [cstData.alias, cstData.cst_type, schema]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
header='Создание конституенты'
|
header='Создание конституенты'
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import { useLayoutEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
import RSInput from '@/components/RSInput';
|
import RSInput from '@/components/RSInput';
|
||||||
|
@ -33,7 +33,6 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
||||||
const showConvention = useMemo(() => !!state.convention || forceComment || isBasic, [state, forceComment, isBasic]);
|
const showConvention = useMemo(() => !!state.convention || forceComment || isBasic, [state, forceComment, isBasic]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
partialUpdate({ alias: generateAlias(state.cst_type, schema) });
|
|
||||||
setForceComment(false);
|
setForceComment(false);
|
||||||
}, [state.cst_type, partialUpdate, schema]);
|
}, [state.cst_type, partialUpdate, schema]);
|
||||||
|
|
||||||
|
@ -43,44 +42,51 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
||||||
}
|
}
|
||||||
}, [state.alias, state.cst_type, schema, setValidated]);
|
}, [state.alias, state.cst_type, schema, setValidated]);
|
||||||
|
|
||||||
|
const handleTypeChange = useCallback(
|
||||||
|
(target: CstType) => partialUpdate({ cst_type: target, alias: generateAlias(target, schema) }),
|
||||||
|
[partialUpdate, schema, generateAlias]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
<div key='dlg_cst_alias_picker' className='flex items-center self-center'>
|
<div key='dlg_cst_alias_picker' className='flex items-center self-center gap-3'>
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
id='dlg_cst_type'
|
id='dlg_cst_type'
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
className='w-[15rem]'
|
className='w-[15rem]'
|
||||||
options={SelectorCstType}
|
options={SelectorCstType}
|
||||||
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
|
value={{ value: state.cst_type, label: labelCstType(state.cst_type) }}
|
||||||
onChange={data => partialUpdate({ cst_type: data?.value ?? CstType.BASE })}
|
onChange={data => handleTypeChange(data?.value ?? CstType.BASE)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
id='dlg_cst_alias'
|
||||||
|
dense
|
||||||
|
label='Имя'
|
||||||
|
className='w-[7rem] mr-8'
|
||||||
|
value={state.alias}
|
||||||
|
onChange={event => partialUpdate({ alias: event.target.value })}
|
||||||
/>
|
/>
|
||||||
<BadgeHelp
|
<BadgeHelp
|
||||||
topic={HelpTopic.CC_CONSTITUENTA}
|
topic={HelpTopic.CC_CONSTITUENTA}
|
||||||
offset={16}
|
offset={16}
|
||||||
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
|
||||||
id='dlg_cst_alias'
|
|
||||||
dense
|
|
||||||
label='Имя'
|
|
||||||
className='w-[7rem] ml-3'
|
|
||||||
value={state.alias}
|
|
||||||
onChange={event => partialUpdate({ alias: event.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<TextArea
|
<TextArea
|
||||||
key='dlg_cst_term'
|
key='dlg_cst_term'
|
||||||
id='dlg_cst_term'
|
id='dlg_cst_term'
|
||||||
|
fitContent
|
||||||
spellCheck
|
spellCheck
|
||||||
label='Термин'
|
label='Термин'
|
||||||
placeholder='Обозначение, используемое в текстовых определениях'
|
placeholder='Обозначение, используемое в текстовых определениях'
|
||||||
className='cc-fit-content max-h-[3.6rem]'
|
className='max-h-[3.6rem]'
|
||||||
value={state.term_raw}
|
value={state.term_raw}
|
||||||
onChange={event => partialUpdate({ term_raw: event.target.value })}
|
onChange={event => partialUpdate({ term_raw: event.target.value })}
|
||||||
/>
|
/>
|
||||||
<AnimateFade key='dlg_cst_expression' hideContent={!state.definition_formal && isElementary}>
|
<AnimateFade key='dlg_cst_expression' hideContent={!state.definition_formal && isElementary}>
|
||||||
<RSInput
|
<RSInput
|
||||||
id='dlg_cst_expression'
|
id='dlg_cst_expression'
|
||||||
|
noTooltip
|
||||||
label={
|
label={
|
||||||
state.cst_type === CstType.STRUCTURED
|
state.cst_type === CstType.STRUCTURED
|
||||||
? 'Область определения'
|
? 'Область определения'
|
||||||
|
@ -102,9 +108,10 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
||||||
<TextArea
|
<TextArea
|
||||||
id='dlg_cst_definition'
|
id='dlg_cst_definition'
|
||||||
spellCheck
|
spellCheck
|
||||||
|
fitContent
|
||||||
label='Текстовое определение'
|
label='Текстовое определение'
|
||||||
placeholder='Текстовая интерпретация формального выражения'
|
placeholder='Текстовая интерпретация формального выражения'
|
||||||
className='cc-fit-content max-h-[3.6rem]'
|
className='max-h-[3.6rem]'
|
||||||
value={state.definition_raw}
|
value={state.definition_raw}
|
||||||
onChange={event => partialUpdate({ definition_raw: event.target.value })}
|
onChange={event => partialUpdate({ definition_raw: event.target.value })}
|
||||||
/>
|
/>
|
||||||
|
@ -126,9 +133,10 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
|
||||||
key='dlg_cst_convention'
|
key='dlg_cst_convention'
|
||||||
id='dlg_cst_convention'
|
id='dlg_cst_convention'
|
||||||
spellCheck
|
spellCheck
|
||||||
|
fitContent
|
||||||
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
||||||
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
|
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
|
||||||
className='cc-fit-content max-h-[5.4rem]'
|
className='max-h-[5.4rem]'
|
||||||
value={state.convention}
|
value={state.convention}
|
||||||
onChange={event => partialUpdate({ convention: event.target.value })}
|
onChange={event => partialUpdate({ convention: event.target.value })}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
|
|
||||||
import BadgeHelp from '@/components/info/BadgeHelp';
|
import BadgeHelp from '@/components/info/BadgeHelp';
|
||||||
|
@ -54,7 +54,10 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
() => inputOperations.map(operation => operation.result).filter(id => id !== null),
|
() => inputOperations.map(operation => operation.result).filter(id => id !== null),
|
||||||
[inputOperations]
|
[inputOperations]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
|
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
|
||||||
|
const [suggestions, setSuggestions] = useState<ICstSubstitute[]>([]);
|
||||||
|
|
||||||
const cache = useRSFormCache();
|
const cache = useRSFormCache();
|
||||||
const schemas = useMemo(
|
const schemas = useMemo(
|
||||||
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
|
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
|
||||||
|
@ -63,11 +66,11 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
|
|
||||||
const canSubmit = useMemo(() => alias !== '', [alias]);
|
const canSubmit = useMemo(() => alias !== '', [alias]);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
cache.preload(schemasIDs);
|
cache.preload(schemasIDs);
|
||||||
}, [schemasIDs]);
|
}, [schemasIDs]);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (cache.loading || schemas.length !== schemasIDs.length) {
|
if (cache.loading || schemas.length !== schemasIDs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -86,13 +89,14 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
);
|
);
|
||||||
}, [schemasIDs, schemas, cache.loading]);
|
}, [schemasIDs, schemas, cache.loading]);
|
||||||
|
|
||||||
useEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (cache.loading || schemas.length !== schemasIDs.length) {
|
if (cache.loading || schemas.length !== schemasIDs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const validator = new SubstitutionValidator(schemas, substitutions);
|
const validator = new SubstitutionValidator(schemas, substitutions);
|
||||||
setIsCorrect(validator.validate());
|
setIsCorrect(validator.validate());
|
||||||
setValidationText(validator.msg);
|
setValidationText(validator.msg);
|
||||||
|
setSuggestions(validator.suggestions);
|
||||||
}, [substitutions, cache.loading, schemas, schemasIDs.length]);
|
}, [substitutions, cache.loading, schemas, schemasIDs.length]);
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
|
@ -151,10 +155,11 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
isCorrect={isCorrect}
|
isCorrect={isCorrect}
|
||||||
substitutions={substitutions}
|
substitutions={substitutions}
|
||||||
setSubstitutions={setSubstitutions}
|
setSubstitutions={setSubstitutions}
|
||||||
|
suggestions={suggestions}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
),
|
),
|
||||||
[cache.loading, cache.error, substitutions, schemas, validationText, isCorrect]
|
[cache.loading, cache.error, substitutions, suggestions, schemas, validationText, isCorrect]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -16,6 +16,7 @@ interface TabSynthesisProps {
|
||||||
schemas: IRSForm[];
|
schemas: IRSForm[];
|
||||||
substitutions: ICstSubstitute[];
|
substitutions: ICstSubstitute[];
|
||||||
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||||
|
suggestions: ICstSubstitute[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function TabSynthesis({
|
function TabSynthesis({
|
||||||
|
@ -25,7 +26,8 @@ function TabSynthesis({
|
||||||
validationText,
|
validationText,
|
||||||
isCorrect,
|
isCorrect,
|
||||||
substitutions,
|
substitutions,
|
||||||
setSubstitutions
|
setSubstitutions,
|
||||||
|
suggestions
|
||||||
}: TabSynthesisProps) {
|
}: TabSynthesisProps) {
|
||||||
const { colors } = useConceptOptions();
|
const { colors } = useConceptOptions();
|
||||||
return (
|
return (
|
||||||
|
@ -36,8 +38,14 @@ function TabSynthesis({
|
||||||
rows={10}
|
rows={10}
|
||||||
substitutions={substitutions}
|
substitutions={substitutions}
|
||||||
setSubstitutions={setSubstitutions}
|
setSubstitutions={setSubstitutions}
|
||||||
|
suggestions={suggestions}
|
||||||
|
/>
|
||||||
|
<TextArea
|
||||||
|
disabled
|
||||||
|
value={validationText}
|
||||||
|
rows={4}
|
||||||
|
style={{ borderColor: isCorrect ? undefined : colors.fgRed }}
|
||||||
/>
|
/>
|
||||||
<TextArea disabled value={validationText} style={{ borderColor: isCorrect ? undefined : colors.fgRed }} />
|
|
||||||
</DataLoader>
|
</DataLoader>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,27 +241,27 @@ export class Graph {
|
||||||
|
|
||||||
topologicalOrder(): number[] {
|
topologicalOrder(): number[] {
|
||||||
const result: number[] = [];
|
const result: number[] = [];
|
||||||
const marked = new Map<number, boolean>();
|
const marked = new Set<number>();
|
||||||
const toVisit: number[] = [];
|
const nodeStack: number[] = [];
|
||||||
this.nodes.forEach(node => {
|
this.nodes.forEach(node => {
|
||||||
if (marked.get(node.id)) {
|
if (marked.has(node.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toVisit.push(node.id);
|
nodeStack.push(node.id);
|
||||||
while (toVisit.length > 0) {
|
while (nodeStack.length > 0) {
|
||||||
const item = toVisit[toVisit.length - 1];
|
const item = nodeStack[nodeStack.length - 1];
|
||||||
if (marked.get(item)) {
|
if (marked.has(item)) {
|
||||||
if (!result.find(id => id === item)) {
|
if (!result.find(id => id === item)) {
|
||||||
result.push(item);
|
result.push(item);
|
||||||
}
|
}
|
||||||
toVisit.pop();
|
nodeStack.pop();
|
||||||
} else {
|
} else {
|
||||||
marked.set(item, true);
|
marked.add(item);
|
||||||
const itemNode = this.nodes.get(item);
|
const itemNode = this.nodes.get(item);
|
||||||
if (itemNode && itemNode.outputs.length > 0) {
|
if (itemNode && itemNode.outputs.length > 0) {
|
||||||
itemNode.outputs.forEach(child => {
|
itemNode.outputs.forEach(child => {
|
||||||
if (!marked.get(child)) {
|
if (!marked.has(child)) {
|
||||||
toVisit.push(child);
|
nodeStack.push(child);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,6 +133,7 @@ export interface IMultiSubstitution {
|
||||||
original: IConstituenta;
|
original: IConstituenta;
|
||||||
substitution: IConstituenta;
|
substitution: IConstituenta;
|
||||||
substitution_source: ILibraryItem;
|
substitution_source: ILibraryItem;
|
||||||
|
is_suggestion: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -213,6 +214,7 @@ export enum SubstitutionErrorType {
|
||||||
typificationCycle,
|
typificationCycle,
|
||||||
baseSubstitutionNotSet,
|
baseSubstitutionNotSet,
|
||||||
unequalTypification,
|
unequalTypification,
|
||||||
|
unequalExpressions,
|
||||||
unequalArgsCount,
|
unequalArgsCount,
|
||||||
unequalArgs
|
unequalArgs
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
* Module: API for OperationSystem.
|
* Module: API for OperationSystem.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { limits } from '@/utils/constants';
|
||||||
import { describeSubstitutionError, information } from '@/utils/labels';
|
import { describeSubstitutionError, information } from '@/utils/labels';
|
||||||
import { TextMatcher } from '@/utils/utils';
|
import { TextMatcher } from '@/utils/utils';
|
||||||
|
|
||||||
import { Graph } from './Graph';
|
import { Graph } from './Graph';
|
||||||
import { ILibraryItem, LibraryItemID } from './library';
|
import { ILibraryItem, LibraryItemID } from './library';
|
||||||
import { ICstSubstitute, IOperation, IOperationSchema, SubstitutionErrorType } from './oss';
|
import { ICstSubstitute, IOperation, IOperationSchema, SubstitutionErrorType } from './oss';
|
||||||
import { ConstituentaID, CstType, IConstituenta, IRSForm } from './rsform';
|
import { ConstituentaID, CstClass, CstType, IConstituenta, IRSForm } from './rsform';
|
||||||
import { AliasMapping, ParsingStatus } from './rslang';
|
import { AliasMapping, ParsingStatus } from './rslang';
|
||||||
import { applyAliasMapping, applyTypificationMapping, extractGlobals, isSetTypification } from './rslangAPI';
|
import { applyAliasMapping, applyTypificationMapping, extractGlobals, isSetTypification } from './rslangAPI';
|
||||||
|
|
||||||
|
@ -51,14 +52,20 @@ export function sortItemsForOSS(oss: IOperationSchema, items: ILibraryItem[]): I
|
||||||
|
|
||||||
type CrossMapping = Map<LibraryItemID, AliasMapping>;
|
type CrossMapping = Map<LibraryItemID, AliasMapping>;
|
||||||
|
|
||||||
|
// TODO: test validator
|
||||||
/**
|
/**
|
||||||
* Validator for Substitution table.
|
* Validator for Substitution table.
|
||||||
*/
|
*/
|
||||||
export class SubstitutionValidator {
|
export class SubstitutionValidator {
|
||||||
public msg: string = '';
|
public msg: string = '';
|
||||||
|
public suggestions: ICstSubstitute[] = [];
|
||||||
|
|
||||||
private schemas: IRSForm[];
|
private schemas: IRSForm[];
|
||||||
private substitutions: ICstSubstitute[];
|
private substitutions: ICstSubstitute[];
|
||||||
|
private constituents = new Set<ConstituentaID>();
|
||||||
|
private originals = new Set<ConstituentaID>();
|
||||||
|
private mapping: CrossMapping = new Map();
|
||||||
|
|
||||||
private cstByID = new Map<ConstituentaID, IConstituenta>();
|
private cstByID = new Map<ConstituentaID, IConstituenta>();
|
||||||
private schemaByID = new Map<LibraryItemID, IRSForm>();
|
private schemaByID = new Map<LibraryItemID, IRSForm>();
|
||||||
private schemaByCst = new Map<ConstituentaID, IRSForm>();
|
private schemaByCst = new Map<ConstituentaID, IRSForm>();
|
||||||
|
@ -66,16 +73,36 @@ export class SubstitutionValidator {
|
||||||
constructor(schemas: IRSForm[], substitutions: ICstSubstitute[]) {
|
constructor(schemas: IRSForm[], substitutions: ICstSubstitute[]) {
|
||||||
this.schemas = schemas;
|
this.schemas = schemas;
|
||||||
this.substitutions = substitutions;
|
this.substitutions = substitutions;
|
||||||
|
if (this.substitutions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
schemas.forEach(schema => {
|
schemas.forEach(schema => {
|
||||||
this.schemaByID.set(schema.id, schema);
|
this.schemaByID.set(schema.id, schema);
|
||||||
|
this.mapping.set(schema.id, {});
|
||||||
schema.items.forEach(item => {
|
schema.items.forEach(item => {
|
||||||
this.cstByID.set(item.id, item);
|
this.cstByID.set(item.id, item);
|
||||||
this.schemaByCst.set(item.id, schema);
|
this.schemaByCst.set(item.id, schema);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
let index = limits.max_semantic_index;
|
||||||
|
substitutions.forEach(item => {
|
||||||
|
this.constituents.add(item.original);
|
||||||
|
this.constituents.add(item.substitution);
|
||||||
|
this.originals.add(item.original);
|
||||||
|
const original = this.cstByID.get(item.original);
|
||||||
|
const substitution = this.cstByID.get(item.substitution);
|
||||||
|
if (!original || !substitution) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
const newAlias = `${substitution.alias[0]}${index}`;
|
||||||
|
this.mapping.get(original.schema)![original.alias] = newAlias;
|
||||||
|
this.mapping.get(substitution.schema)![substitution.alias] = newAlias;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public validate(): boolean {
|
public validate(): boolean {
|
||||||
|
this.calculateSuggestions();
|
||||||
if (this.substitutions.length === 0) {
|
if (this.substitutions.length === 0) {
|
||||||
return this.setValid();
|
return this.setValid();
|
||||||
}
|
}
|
||||||
|
@ -85,13 +112,62 @@ export class SubstitutionValidator {
|
||||||
if (!this.checkCycles()) {
|
if (!this.checkCycles()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this.checkTypifications()) {
|
if (!this.checkSubstitutions()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.setValid();
|
return this.setValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private calculateSuggestions(): void {
|
||||||
|
const candidates = new Map<ConstituentaID, string>();
|
||||||
|
const minors = new Set<ConstituentaID>();
|
||||||
|
const schemaByCst = new Map<ConstituentaID, IRSForm>();
|
||||||
|
for (const schema of this.schemas) {
|
||||||
|
for (const cst of schema.items) {
|
||||||
|
if (this.originals.has(cst.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cst.cst_class === CstClass.BASIC) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const inputs = schema.graph.at(cst.id)!.inputs;
|
||||||
|
if (inputs.length === 0 || inputs.some(id => !this.constituents.has(id))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inputs.some(id => this.originals.has(id))) {
|
||||||
|
minors.add(cst.id);
|
||||||
|
}
|
||||||
|
candidates.set(cst.id, applyAliasMapping(cst.definition_formal, this.mapping.get(schema.id)!).replace(' ', ''));
|
||||||
|
schemaByCst.set(cst.id, schema);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [key1, value1] of candidates) {
|
||||||
|
for (const [key2, value2] of candidates) {
|
||||||
|
if (key1 >= key2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (schemaByCst.get(key1) === schemaByCst.get(key2)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (value1 != value2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (minors.has(key2)) {
|
||||||
|
this.suggestions.push({
|
||||||
|
original: key2,
|
||||||
|
substitution: key1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.suggestions.push({
|
||||||
|
original: key1,
|
||||||
|
substitution: key2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private checkTypes(): boolean {
|
private checkTypes(): boolean {
|
||||||
for (const item of this.substitutions) {
|
for (const item of this.substitutions) {
|
||||||
const original = this.cstByID.get(item.original);
|
const original = this.cstByID.get(item.original);
|
||||||
|
@ -204,7 +280,7 @@ export class SubstitutionValidator {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkTypifications(): boolean {
|
private checkSubstitutions(): boolean {
|
||||||
const baseMappings = this.prepareBaseMappings();
|
const baseMappings = this.prepareBaseMappings();
|
||||||
const typeMappings = this.calculateSubstituteMappings(baseMappings);
|
const typeMappings = this.calculateSubstituteMappings(baseMappings);
|
||||||
if (typeMappings === null) {
|
if (typeMappings === null) {
|
||||||
|
@ -216,6 +292,13 @@ export class SubstitutionValidator {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const substitution = this.cstByID.get(item.substitution)!;
|
const substitution = this.cstByID.get(item.substitution)!;
|
||||||
|
if (original.cst_type === substitution.cst_type && original.cst_class !== CstClass.BASIC) {
|
||||||
|
if (!this.checkEqual(original, substitution)) {
|
||||||
|
this.reportError(SubstitutionErrorType.unequalExpressions, [substitution.alias, original.alias]);
|
||||||
|
// Note: do not interrupt the validation process. Only warn about the problem.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const originalType = applyTypificationMapping(
|
const originalType = applyTypificationMapping(
|
||||||
applyAliasMapping(original.parse.typification, baseMappings.get(original.schema)!),
|
applyAliasMapping(original.parse.typification, baseMappings.get(original.schema)!),
|
||||||
typeMappings
|
typeMappings
|
||||||
|
@ -287,7 +370,6 @@ export class SubstitutionValidator {
|
||||||
} else {
|
} else {
|
||||||
substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!);
|
substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!);
|
||||||
substitutionText = applyTypificationMapping(substitutionText, result);
|
substitutionText = applyTypificationMapping(substitutionText, result);
|
||||||
console.log(substitutionText);
|
|
||||||
if (!isSetTypification(substitutionText)) {
|
if (!isSetTypification(substitutionText)) {
|
||||||
this.reportError(SubstitutionErrorType.baseSubstitutionNotSet, [
|
this.reportError(SubstitutionErrorType.baseSubstitutionNotSet, [
|
||||||
substitution.alias,
|
substitution.alias,
|
||||||
|
@ -310,13 +392,35 @@ export class SubstitutionValidator {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkEqual(left: IConstituenta, right: IConstituenta): boolean {
|
||||||
|
const schema1 = this.schemaByID.get(left.schema)!;
|
||||||
|
const inputs1 = schema1.graph.at(left.id)!.inputs;
|
||||||
|
if (inputs1.some(id => !this.constituents.has(id))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const schema2 = this.schemaByID.get(right.schema)!;
|
||||||
|
const inputs2 = schema2.graph.at(right.id)!.inputs;
|
||||||
|
if (inputs2.some(id => !this.constituents.has(id))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const expression1 = applyAliasMapping(left.definition_formal, this.mapping.get(schema1.id)!);
|
||||||
|
const expression2 = applyAliasMapping(right.definition_formal, this.mapping.get(schema2.id)!);
|
||||||
|
return expression1.replace(' ', '') === expression2.replace(' ', '');
|
||||||
|
}
|
||||||
|
|
||||||
private setValid(): boolean {
|
private setValid(): boolean {
|
||||||
this.msg = information.substitutionsCorrect;
|
if (this.msg.length > 0) {
|
||||||
|
this.msg += '\n';
|
||||||
|
}
|
||||||
|
this.msg += information.substitutionsCorrect;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private reportError(errorType: SubstitutionErrorType, params: string[]): boolean {
|
private reportError(errorType: SubstitutionErrorType, params: string[]): boolean {
|
||||||
this.msg = describeSubstitutionError({
|
if (this.msg.length > 0) {
|
||||||
|
this.msg += '\n';
|
||||||
|
}
|
||||||
|
this.msg += describeSubstitutionError({
|
||||||
errorType: errorType,
|
errorType: errorType,
|
||||||
params: params
|
params: params
|
||||||
});
|
});
|
||||||
|
|
|
@ -93,7 +93,7 @@ export function inferTemplate(expression: string): boolean {
|
||||||
* - `CstClass.DERIVED` if the CstType is TERM, FUNCTION, or PREDICATE.
|
* - `CstClass.DERIVED` if the CstType is TERM, FUNCTION, or PREDICATE.
|
||||||
* - `CstClass.STATEMENT` if the CstType is AXIOM or THEOREM.
|
* - `CstClass.STATEMENT` if the CstType is AXIOM or THEOREM.
|
||||||
*/
|
*/
|
||||||
export function inferClass(type: CstType, isTemplate: boolean): CstClass {
|
export function inferClass(type: CstType, isTemplate: boolean = false): CstClass {
|
||||||
if (isTemplate) {
|
if (isTemplate) {
|
||||||
return CstClass.TEMPLATE;
|
return CstClass.TEMPLATE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
|
import { applyTypificationMapping, extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
|
||||||
|
|
||||||
const globalsData = [
|
const globalsData = [
|
||||||
['', ''],
|
['', ''],
|
||||||
|
@ -47,3 +47,24 @@ describe('Testing split template', () => {
|
||||||
expect(`${result.head}||${result.body}`).toBe(expected);
|
expect(`${result.head}||${result.body}`).toBe(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const typificationMappingData = [
|
||||||
|
['', '', '', ''],
|
||||||
|
['X1', 'X2', 'X1', 'X2'],
|
||||||
|
['X1', 'X2', 'ℬ(X1)', 'ℬ(X2)'],
|
||||||
|
['X1', 'X2', 'X1×X3', 'X2×X3'],
|
||||||
|
['X1', '(X1×X1)', 'X1', 'X1×X1'],
|
||||||
|
['X1', '(X1×X1)', 'ℬ(X1)', 'ℬ(X1×X1)'],
|
||||||
|
['X1', '(X1×X1)', 'ℬ(X1×X2)', 'ℬ((X1×X1)×X2)'],
|
||||||
|
['X1', 'ℬ(X3)', 'ℬ(X1)', 'ℬℬ(X3)'],
|
||||||
|
['X1', 'ℬ(X3)', 'ℬ(X1×X1)', 'ℬ(ℬ(X3)×ℬ(X3))']
|
||||||
|
];
|
||||||
|
describe('Testing typification mapping', () => {
|
||||||
|
it.each(typificationMappingData)(
|
||||||
|
'Apply typification mapping %p',
|
||||||
|
(original: string, replacement: string, input: string, expected: string) => {
|
||||||
|
const result = applyTypificationMapping(input, { [original]: replacement });
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -164,13 +164,55 @@ export function applyAliasMapping(target: string, mapping: AliasMapping): string
|
||||||
* Apply alias typification mapping.
|
* Apply alias typification mapping.
|
||||||
*/
|
*/
|
||||||
export function applyTypificationMapping(target: string, mapping: AliasMapping): string {
|
export function applyTypificationMapping(target: string, mapping: AliasMapping): string {
|
||||||
const result = applyAliasMapping(target, mapping);
|
const modified = applyAliasMapping(target, mapping);
|
||||||
if (result === target) {
|
if (modified === target) {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove double parentheses
|
const deleteBrackets: number[] = [];
|
||||||
// deal with ℬ(ℬ)
|
const positions: number[] = [];
|
||||||
|
const booleans: number[] = [];
|
||||||
|
let boolCount: number = 0;
|
||||||
|
let stackSize: number = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < modified.length; i++) {
|
||||||
|
const char = modified[i];
|
||||||
|
if (char === 'ℬ') {
|
||||||
|
boolCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (char === '(') {
|
||||||
|
stackSize++;
|
||||||
|
positions.push(i);
|
||||||
|
booleans.push(boolCount);
|
||||||
|
}
|
||||||
|
boolCount = 0;
|
||||||
|
if (char === ')') {
|
||||||
|
if (
|
||||||
|
i < modified.length - 1 &&
|
||||||
|
modified[i + 1] === ')' &&
|
||||||
|
stackSize > 1 &&
|
||||||
|
positions[stackSize - 2] + booleans[stackSize - 1] + 1 === positions[stackSize - 1]
|
||||||
|
) {
|
||||||
|
deleteBrackets.push(i);
|
||||||
|
deleteBrackets.push(positions[stackSize - 2]);
|
||||||
|
}
|
||||||
|
if (i === modified.length - 1 && stackSize === 1 && positions[0] === 0) {
|
||||||
|
deleteBrackets.push(i);
|
||||||
|
deleteBrackets.push(positions[0]);
|
||||||
|
}
|
||||||
|
stackSize--;
|
||||||
|
positions.pop();
|
||||||
|
booleans.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
for (let i = 0; i < modified.length; i++) {
|
||||||
|
if (!deleteBrackets.includes(i)) {
|
||||||
|
result += modified[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,16 +9,17 @@ import TopicItem from '../TopicItem';
|
||||||
|
|
||||||
function HelpMain() {
|
function HelpMain() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='text-justify'>
|
||||||
<h1>Портал</h1>
|
<h1>Портал</h1>
|
||||||
<p>
|
<p>
|
||||||
Портал позволяет анализировать предметные области, формально записывать системы определений и синтезировать их с
|
Портал позволяет анализировать предметные области, формально записывать системы определений и синтезировать их с
|
||||||
помощью математического аппарата <LinkTopic text='Родов структур' topic={HelpTopic.RSLANG} />
|
помощью математического аппарата <LinkTopic text='Родов структур' topic={HelpTopic.RSLANG} />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Такие системы называются <b>Концептуальными схемами</b> и состоят из отдельных{' '}
|
Такие системы называются <LinkTopic text='Концептуальными схемами' topic={HelpTopic.CC_SYSTEM} /> и состоят из
|
||||||
<LinkTopic text='Конституент' topic={HelpTopic.CC_CONSTITUENTA} />, обладающих уникальными обозначениями и
|
отдельных <LinkTopic text='Конституент' topic={HelpTopic.CC_CONSTITUENTA} />, обладающих уникальными
|
||||||
формальными определениями
|
обозначениями и формальными определениями. Концептуальные схемы могут быть получены в рамках операций синтеза в{' '}
|
||||||
|
<LinkTopic text='Операционной схеме синтеза' topic={HelpTopic.CC_OSS} />.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Разделы Портала</h2>
|
<h2>Разделы Портала</h2>
|
||||||
|
@ -69,8 +70,9 @@ function HelpMain() {
|
||||||
Портал разрабатывается <TextURL text='Центром Концепт' href={external_urls.concept} />
|
Портал разрабатывается <TextURL text='Центром Концепт' href={external_urls.concept} />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Портал поддерживает актуальные версии браузеров Chrome, Firefox, Safari. Убедитесь, что используете последнюю
|
Портал поддерживает актуальные версии браузеров Chrome, Firefox, Safari, включая мобильные устройства.
|
||||||
версию браузера в случае возникновения визуальных ошибок или проблем с производительностью.
|
Убедитесь, что используете последнюю версию браузера в случае возникновения визуальных ошибок или проблем с
|
||||||
|
производительностью.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Ваши пожелания по доработке, найденные ошибки и иные предложения можно направлять на email:{' '}
|
Ваши пожелания по доработке, найденные ошибки и иные предложения можно направлять на email:{' '}
|
||||||
|
|
|
@ -181,7 +181,7 @@ function HelpThesaurus() {
|
||||||
</li>
|
</li>
|
||||||
<li>основа данного понятия – понятие, на котором основано порождающее выражение данной конституенты;</li>
|
<li>основа данного понятия – понятие, на котором основано порождающее выражение данной конституенты;</li>
|
||||||
<li>
|
<li>
|
||||||
порожденное понятие данным понятием – понятие, определение которого является порождающим выражением,
|
порожденное понятие данным понятием – понятие, определением которого является порождающим выражением,
|
||||||
основанным на данном понятии.
|
основанным на данном понятии.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -267,7 +267,7 @@ function HelpThesaurus() {
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconSynthesis size='1rem' className='inline-icon' />
|
<IconSynthesis size='1rem' className='inline-icon' />
|
||||||
{'\u2009'}синтез концептуальных схем.ыф
|
{'\u2009'}синтез концептуальных схем.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,9 @@ function HelpConceptOSS() {
|
||||||
<p>
|
<p>
|
||||||
Операция синтеза в рамках ОСС задаются набором операций-аргументов и <b>таблицей отождествлений</b> понятий из
|
Операция синтеза в рамках ОСС задаются набором операций-аргументов и <b>таблицей отождествлений</b> понятий из
|
||||||
КС, привязанных к выбранным аргументам. Таким образом{' '}
|
КС, привязанных к выбранным аргументам. Таким образом{' '}
|
||||||
<LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на исходные
|
<LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на исходные и
|
||||||
(дописанные), наследованные, отождествленные (удаляемые).
|
наследованные. При формировании таблицы отождествлений пользователю предлагается синтезировать производные
|
||||||
|
понятия, выражения которых совпадают после проведения заданных отождествлений.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
После задания аргументов и таблицы отождествления необходимо единожды{' '}
|
После задания аргументов и таблицы отождествления необходимо единожды{' '}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { AnimatePresence } from 'framer-motion';
|
||||||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { IconChild, IconParent, IconSave } from '@/components/Icons';
|
import { IconChild, IconPredecessor, IconSave } from '@/components/Icons';
|
||||||
import RefsInput from '@/components/RefsInput';
|
import RefsInput from '@/components/RefsInput';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
@ -128,7 +128,7 @@ function FormConstituenta({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimateFade className='mx-0 md:mx-auto'>
|
<AnimateFade className='mx-0 md:mx-auto pt-[2rem] xs:pt-0'>
|
||||||
{state ? (
|
{state ? (
|
||||||
<ControlsOverlay
|
<ControlsOverlay
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -161,7 +161,7 @@ function FormConstituenta({
|
||||||
{state ? (
|
{state ? (
|
||||||
<TextArea
|
<TextArea
|
||||||
id='cst_typification'
|
id='cst_typification'
|
||||||
className='cc-fit-content'
|
fitContent
|
||||||
dense
|
dense
|
||||||
noResize
|
noResize
|
||||||
noBorder
|
noBorder
|
||||||
|
@ -171,100 +171,103 @@ function FormConstituenta({
|
||||||
colors='clr-app clr-text-default'
|
colors='clr-app clr-text-default'
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<AnimatePresence>
|
{state ? (
|
||||||
<AnimateFade key='cst_expression_fade' hideContent={!state || (!state?.definition_formal && isElementary)}>
|
<AnimatePresence>
|
||||||
<EditorRSExpression
|
<AnimateFade key='cst_expression_fade' hideContent={!state.definition_formal && isElementary}>
|
||||||
id='cst_expression'
|
<EditorRSExpression
|
||||||
label={
|
id='cst_expression'
|
||||||
state?.cst_type === CstType.STRUCTURED
|
label={
|
||||||
? 'Область определения'
|
state.cst_type === CstType.STRUCTURED
|
||||||
: !!state && isFunctional(state.cst_type)
|
? 'Область определения'
|
||||||
? 'Определение функции'
|
: isFunctional(state.cst_type)
|
||||||
: 'Формальное определение'
|
? 'Определение функции'
|
||||||
}
|
: 'Формальное определение'
|
||||||
placeholder={
|
}
|
||||||
state?.cst_type !== CstType.STRUCTURED
|
placeholder={
|
||||||
? 'Родоструктурное выражение'
|
state.cst_type !== CstType.STRUCTURED
|
||||||
: 'Определение множества, которому принадлежат элементы родовой структуры'
|
? 'Родоструктурное выражение'
|
||||||
}
|
: 'Определение множества, которому принадлежат элементы родовой структуры'
|
||||||
value={expression}
|
}
|
||||||
activeCst={state}
|
value={expression}
|
||||||
disabled={disabled || state?.is_inherited}
|
activeCst={state}
|
||||||
toggleReset={toggleReset}
|
disabled={disabled || state.is_inherited}
|
||||||
onChange={newValue => setExpression(newValue)}
|
toggleReset={toggleReset}
|
||||||
setTypification={setTypification}
|
onChange={newValue => setExpression(newValue)}
|
||||||
onOpenEdit={onOpenEdit}
|
setTypification={setTypification}
|
||||||
/>
|
onOpenEdit={onOpenEdit}
|
||||||
</AnimateFade>
|
|
||||||
<AnimateFade key='cst_definition_fade' hideContent={!state || (!state?.definition_raw && isElementary)}>
|
|
||||||
<RefsInput
|
|
||||||
id='cst_definition'
|
|
||||||
label='Текстовое определение'
|
|
||||||
placeholder='Текстовая интерпретация формального выражения'
|
|
||||||
minHeight='3.75rem'
|
|
||||||
maxHeight='8rem'
|
|
||||||
schema={schema}
|
|
||||||
onOpenEdit={onOpenEdit}
|
|
||||||
value={textDefinition}
|
|
||||||
initialValue={state?.definition_raw ?? ''}
|
|
||||||
resolved={state?.definition_resolved ?? ''}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={newValue => setTextDefinition(newValue)}
|
|
||||||
/>
|
|
||||||
</AnimateFade>
|
|
||||||
<AnimateFade key='cst_convention_fade' hideContent={!showConvention || !state}>
|
|
||||||
<TextArea
|
|
||||||
id='cst_convention'
|
|
||||||
className='cc-fit-content max-h-[8rem]'
|
|
||||||
spellCheck
|
|
||||||
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
|
||||||
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
|
|
||||||
value={convention}
|
|
||||||
disabled={disabled || (isBasic && state?.is_inherited)}
|
|
||||||
onChange={event => setConvention(event.target.value)}
|
|
||||||
/>
|
|
||||||
</AnimateFade>
|
|
||||||
<AnimateFade key='cst_convention_button' hideContent={showConvention || (disabled && !processing)}>
|
|
||||||
<button
|
|
||||||
key='cst_disable_comment'
|
|
||||||
id='cst_disable_comment'
|
|
||||||
type='button'
|
|
||||||
tabIndex={-1}
|
|
||||||
className='self-start cc-label clr-text-url hover:underline'
|
|
||||||
onClick={() => setForceComment(true)}
|
|
||||||
>
|
|
||||||
Добавить комментарий
|
|
||||||
</button>
|
|
||||||
</AnimateFade>
|
|
||||||
|
|
||||||
{!disabled || processing ? (
|
|
||||||
<div className='self-center flex'>
|
|
||||||
<SubmitButton
|
|
||||||
key='cst_form_submit'
|
|
||||||
id='cst_form_submit'
|
|
||||||
text='Сохранить изменения'
|
|
||||||
disabled={disabled || !isModified}
|
|
||||||
icon={<IconSave size='1.25rem' />}
|
|
||||||
/>
|
/>
|
||||||
<Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'>
|
</AnimateFade>
|
||||||
{state?.is_inherited_parent ? (
|
<AnimateFade key='cst_definition_fade' hideContent={!state.definition_raw && isElementary}>
|
||||||
<MiniButton
|
<RefsInput
|
||||||
icon={<IconChild size='1.25rem' className='clr-text-red' />}
|
id='cst_definition'
|
||||||
disabled
|
label='Текстовое определение'
|
||||||
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'
|
placeholder='Текстовая интерпретация формального выражения'
|
||||||
/>
|
minHeight='3.75rem'
|
||||||
) : null}
|
maxHeight='8rem'
|
||||||
{state?.is_inherited ? (
|
schema={schema}
|
||||||
<MiniButton
|
onOpenEdit={onOpenEdit}
|
||||||
icon={<IconParent size='1.25rem' className='clr-text-red' />}
|
value={textDefinition}
|
||||||
disabled
|
initialValue={state.definition_raw}
|
||||||
titleHtml='Внимание!</br> Конституента является наследником<br/>'
|
resolved={state.definition_resolved}
|
||||||
/>
|
disabled={disabled}
|
||||||
) : null}
|
onChange={newValue => setTextDefinition(newValue)}
|
||||||
</Overlay>
|
/>
|
||||||
</div>
|
</AnimateFade>
|
||||||
) : null}
|
<AnimateFade key='cst_convention_fade' hideContent={!showConvention}>
|
||||||
</AnimatePresence>
|
<TextArea
|
||||||
|
id='cst_convention'
|
||||||
|
fitContent
|
||||||
|
className='max-h-[8rem]'
|
||||||
|
spellCheck
|
||||||
|
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
||||||
|
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
|
||||||
|
value={convention}
|
||||||
|
disabled={disabled || (isBasic && state.is_inherited)}
|
||||||
|
onChange={event => setConvention(event.target.value)}
|
||||||
|
/>
|
||||||
|
</AnimateFade>
|
||||||
|
<AnimateFade key='cst_convention_button' hideContent={showConvention || (disabled && !processing)}>
|
||||||
|
<button
|
||||||
|
key='cst_disable_comment'
|
||||||
|
id='cst_disable_comment'
|
||||||
|
type='button'
|
||||||
|
tabIndex={-1}
|
||||||
|
className='self-start cc-label clr-text-url hover:underline'
|
||||||
|
onClick={() => setForceComment(true)}
|
||||||
|
>
|
||||||
|
Добавить комментарий
|
||||||
|
</button>
|
||||||
|
</AnimateFade>
|
||||||
|
|
||||||
|
{!disabled || processing ? (
|
||||||
|
<div className='self-center flex'>
|
||||||
|
<SubmitButton
|
||||||
|
key='cst_form_submit'
|
||||||
|
id='cst_form_submit'
|
||||||
|
text='Сохранить изменения'
|
||||||
|
disabled={disabled || !isModified}
|
||||||
|
icon={<IconSave size='1.25rem' />}
|
||||||
|
/>
|
||||||
|
<Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'>
|
||||||
|
{state.is_inherited_parent ? (
|
||||||
|
<MiniButton
|
||||||
|
icon={<IconPredecessor size='1.25rem' className='clr-text-red' />}
|
||||||
|
disabled
|
||||||
|
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{state.is_inherited ? (
|
||||||
|
<MiniButton
|
||||||
|
icon={<IconChild size='1.25rem' className='clr-text-red' />}
|
||||||
|
disabled
|
||||||
|
titleHtml='Внимание!</br> Конституента является наследником<br/>'
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Overlay>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</AnimatePresence>
|
||||||
|
) : null}
|
||||||
</form>
|
</form>
|
||||||
</AnimateFade>
|
</AnimateFade>
|
||||||
);
|
);
|
||||||
|
|
|
@ -50,8 +50,8 @@ function ToolbarConstituenta({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
position='top-1 right-1/2 translate-x-1/2 sm:right-4 sm:translate-x-0 md:right-1/2 md:translate-x-1/2'
|
position='top-1 right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
|
||||||
className='cc-icons outline-none transition-all duration-500'
|
className='cc-icons outline-none transition-all duration-500'
|
||||||
>
|
>
|
||||||
{controller.schema && controller.schema?.oss.length > 0 ? (
|
{controller.schema && controller.schema?.oss.length > 0 ? (
|
||||||
<MiniSelectorOSS
|
<MiniSelectorOSS
|
||||||
|
|
|
@ -28,7 +28,7 @@ import ToolbarRSExpression from './ToolbarRSExpression';
|
||||||
|
|
||||||
interface EditorRSExpressionProps {
|
interface EditorRSExpressionProps {
|
||||||
id?: string;
|
id?: string;
|
||||||
activeCst?: IConstituenta;
|
activeCst: IConstituenta;
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -70,13 +70,10 @@ function EditorRSExpression({
|
||||||
|
|
||||||
function handleChange(newValue: string) {
|
function handleChange(newValue: string) {
|
||||||
onChange(newValue);
|
onChange(newValue);
|
||||||
setIsModified(newValue !== activeCst?.definition_formal);
|
setIsModified(newValue !== activeCst.definition_formal);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
|
function handleCheckExpression(callback?: (parse: IExpressionParse) => void) {
|
||||||
if (!activeCst) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const prefix = getDefinitionPrefix(activeCst);
|
const prefix = getDefinitionPrefix(activeCst);
|
||||||
const expression = prefix + value;
|
const expression = prefix + value;
|
||||||
parser.checkExpression(expression, activeCst, parse => {
|
parser.checkExpression(expression, activeCst, parse => {
|
||||||
|
@ -99,7 +96,7 @@ function EditorRSExpression({
|
||||||
|
|
||||||
const onShowError = useCallback(
|
const onShowError = useCallback(
|
||||||
(error: IRSErrorDescription) => {
|
(error: IRSErrorDescription) => {
|
||||||
if (!activeCst || !rsInput.current) {
|
if (!rsInput.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const prefix = getDefinitionPrefix(activeCst);
|
const prefix = getDefinitionPrefix(activeCst);
|
||||||
|
@ -136,7 +133,7 @@ function EditorRSExpression({
|
||||||
toast.error(errors.astFailed);
|
toast.error(errors.astFailed);
|
||||||
} else {
|
} else {
|
||||||
setSyntaxTree(parse.ast);
|
setSyntaxTree(parse.ast);
|
||||||
setExpression(getDefinitionPrefix(activeCst!) + value);
|
setExpression(getDefinitionPrefix(activeCst) + value);
|
||||||
setShowAST(true);
|
setShowAST(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -145,7 +142,7 @@ function EditorRSExpression({
|
||||||
const controls = useMemo(
|
const controls = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<RSEditorControls
|
<RSEditorControls
|
||||||
isOpen={showControls && (!disabled || (model.processing && !activeCst?.is_inherited))}
|
isOpen={showControls && (!disabled || (model.processing && !activeCst.is_inherited))}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onEdit={handleEdit}
|
onEdit={handleEdit}
|
||||||
/>
|
/>
|
||||||
|
@ -172,7 +169,7 @@ function EditorRSExpression({
|
||||||
<StatusBar
|
<StatusBar
|
||||||
processing={parser.processing}
|
processing={parser.processing}
|
||||||
isModified={isModified}
|
isModified={isModified}
|
||||||
constituenta={activeCst}
|
activeCst={activeCst}
|
||||||
parseData={parser.parseData}
|
parseData={parser.parseData}
|
||||||
onAnalyze={() => handleCheckExpression()}
|
onAnalyze={() => handleCheckExpression()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -19,11 +19,11 @@ interface StatusBarProps {
|
||||||
processing?: boolean;
|
processing?: boolean;
|
||||||
isModified?: boolean;
|
isModified?: boolean;
|
||||||
parseData?: IExpressionParse;
|
parseData?: IExpressionParse;
|
||||||
constituenta?: IConstituenta;
|
activeCst: IConstituenta;
|
||||||
onAnalyze: () => void;
|
onAnalyze: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze }: StatusBarProps) {
|
function StatusBar({ isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) {
|
||||||
const { colors } = useConceptOptions();
|
const { colors } = useConceptOptions();
|
||||||
const status = useMemo(() => {
|
const status = useMemo(() => {
|
||||||
if (isModified) {
|
if (isModified) {
|
||||||
|
@ -33,8 +33,8 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
|
||||||
const parse = parseData.parseResult ? ParsingStatus.VERIFIED : ParsingStatus.INCORRECT;
|
const parse = parseData.parseResult ? ParsingStatus.VERIFIED : ParsingStatus.INCORRECT;
|
||||||
return inferStatus(parse, parseData.valueClass);
|
return inferStatus(parse, parseData.valueClass);
|
||||||
}
|
}
|
||||||
return inferStatus(constituenta?.parse?.status, constituenta?.parse?.valueClass);
|
return inferStatus(activeCst.parse.status, activeCst.parse.valueClass);
|
||||||
}, [isModified, constituenta, parseData]);
|
}, [isModified, activeCst, parseData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -437,10 +437,14 @@ export const RSEditState = ({
|
||||||
|
|
||||||
const createCst = useCallback(
|
const createCst = useCallback(
|
||||||
(type: CstType | undefined, skipDialog: boolean, definition?: string) => {
|
(type: CstType | undefined, skipDialog: boolean, definition?: string) => {
|
||||||
|
if (!model.schema) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetType = type ?? activeCst?.cst_type ?? CstType.BASE;
|
||||||
const data: ICstCreateData = {
|
const data: ICstCreateData = {
|
||||||
insert_after: activeCst?.id ?? null,
|
insert_after: activeCst?.id ?? null,
|
||||||
cst_type: type ?? activeCst?.cst_type ?? CstType.BASE,
|
cst_type: targetType,
|
||||||
alias: '',
|
alias: generateAlias(targetType, model.schema),
|
||||||
term_raw: '',
|
term_raw: '',
|
||||||
definition_formal: definition ?? '',
|
definition_formal: definition ?? '',
|
||||||
definition_raw: '',
|
definition_raw: '',
|
||||||
|
@ -454,17 +458,17 @@ export const RSEditState = ({
|
||||||
setShowCreateCst(true);
|
setShowCreateCst(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeCst, handleCreateCst]
|
[activeCst, handleCreateCst, model.schema]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cloneCst = useCallback(() => {
|
const cloneCst = useCallback(() => {
|
||||||
if (!activeCst) {
|
if (!activeCst || !model.schema) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data: ICstCreateData = {
|
const data: ICstCreateData = {
|
||||||
insert_after: activeCst.id,
|
insert_after: activeCst.id,
|
||||||
cst_type: activeCst.cst_type,
|
cst_type: activeCst.cst_type,
|
||||||
alias: '',
|
alias: generateAlias(activeCst.cst_type, model.schema),
|
||||||
term_raw: activeCst.term_raw,
|
term_raw: activeCst.term_raw,
|
||||||
definition_formal: activeCst.definition_formal,
|
definition_formal: activeCst.definition_formal,
|
||||||
definition_raw: activeCst.definition_raw,
|
definition_raw: activeCst.definition_raw,
|
||||||
|
@ -472,7 +476,7 @@ export const RSEditState = ({
|
||||||
term_forms: activeCst.term_forms
|
term_forms: activeCst.term_forms
|
||||||
};
|
};
|
||||||
handleCreateCst(data);
|
handleCreateCst(data);
|
||||||
}, [activeCst, handleCreateCst]);
|
}, [activeCst, handleCreateCst, model.schema]);
|
||||||
|
|
||||||
const renameCst = useCallback(() => {
|
const renameCst = useCallback(() => {
|
||||||
if (!activeCst) {
|
if (!activeCst) {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const PARAMETER = {
|
||||||
typificationTruncate: 42, // characters - threshold for long typification - truncate
|
typificationTruncate: 42, // characters - threshold for long typification - truncate
|
||||||
|
|
||||||
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: 32, // characters - threshold for long labels - truncate
|
||||||
|
|
||||||
statSmallThreshold: 3, // characters - threshold for small labels - small font
|
statSmallThreshold: 3, // characters - threshold for small labels - small font
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ export const PARAMETER = {
|
||||||
* Numeric limitations.
|
* Numeric limitations.
|
||||||
*/
|
*/
|
||||||
export const limits = {
|
export const limits = {
|
||||||
location_len: 500
|
location_len: 500,
|
||||||
|
max_semantic_index: 900
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -824,6 +824,8 @@ export function describeSubstitutionError(error: ISubstitutionErrorDescription):
|
||||||
return `Ошибка ${error.params[0]} -> ${error.params[1]}: количество аргументов не совпадает`;
|
return `Ошибка ${error.params[0]} -> ${error.params[1]}: количество аргументов не совпадает`;
|
||||||
case SubstitutionErrorType.unequalArgs:
|
case SubstitutionErrorType.unequalArgs:
|
||||||
return `Ошибка ${error.params[0]} -> ${error.params[1]}: типизация аргументов не совпадает`;
|
return `Ошибка ${error.params[0]} -> ${error.params[1]}: типизация аргументов не совпадает`;
|
||||||
|
case SubstitutionErrorType.unequalExpressions:
|
||||||
|
return `Предупреждение ${error.params[0]} -> ${error.params[1]}: определения понятий не совпадают`;
|
||||||
}
|
}
|
||||||
return 'UNKNOWN ERROR';
|
return 'UNKNOWN ERROR';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
import defaultTheme from 'tailwindcss/defaultTheme';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
content: ['./src/**/*.{js,jsx,ts,tsx}'],
|
||||||
|
@ -14,6 +16,10 @@ export default {
|
||||||
modalControls: '70',
|
modalControls: '70',
|
||||||
modalTooltip: '90'
|
modalTooltip: '90'
|
||||||
},
|
},
|
||||||
|
screens: {
|
||||||
|
xs: '475px',
|
||||||
|
...defaultTheme.screens
|
||||||
|
},
|
||||||
extend: {}
|
extend: {}
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user