mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
F: Add suggestions to substitution table
This commit is contained in:
parent
fa6eb49172
commit
2fb64ef69a
|
@ -83,7 +83,6 @@ export { LuNewspaper as IconDefinition } from 'react-icons/lu';
|
|||
export { LuDna as IconTerminology } from 'react-icons/lu';
|
||||
export { FaRegHandshake as IconConvention } from 'react-icons/fa6';
|
||||
export { LiaCloneSolid as IconChild } from 'react-icons/lia';
|
||||
export { RiParentLine as IconParent } from 'react-icons/ri';
|
||||
export { TbTopologyRing as IconConsolidation } from 'react-icons/tb';
|
||||
export { BiSpa as IconPredecessor } from 'react-icons/bi';
|
||||
export { LuArchive as IconArchive } from 'react-icons/lu';
|
||||
|
|
|
@ -5,7 +5,7 @@ import { toast } from 'react-toastify';
|
|||
|
||||
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
|
||||
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 { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { ILibraryItem } from '@/models/library';
|
||||
|
@ -13,13 +13,14 @@ import { ICstSubstitute, IMultiSubstitution } from '@/models/oss';
|
|||
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
|
||||
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 SelectLibraryItem from './SelectLibraryItem';
|
||||
|
||||
interface PickSubstitutionsProps {
|
||||
substitutions: ICstSubstitute[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||
suggestions?: ICstSubstitute[];
|
||||
|
||||
prefixID: string;
|
||||
rows?: number;
|
||||
|
@ -34,6 +35,7 @@ const columnHelper = createColumnHelper<IMultiSubstitution>();
|
|||
function PickSubstitutions({
|
||||
substitutions,
|
||||
setSubstitutions,
|
||||
suggestions,
|
||||
prefixID,
|
||||
rows,
|
||||
schemas,
|
||||
|
@ -55,6 +57,15 @@ function PickSubstitutions({
|
|||
const [deleteRight, setDeleteRight] = useState(true);
|
||||
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(
|
||||
(id: ConstituentaID): IRSForm | undefined => {
|
||||
for (const schema of schemas) {
|
||||
|
@ -82,14 +93,23 @@ function PickSubstitutions({
|
|||
);
|
||||
|
||||
const substitutionData: IMultiSubstitution[] = useMemo(
|
||||
() =>
|
||||
substitutions.map(item => ({
|
||||
() => [
|
||||
...substitutions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
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() {
|
||||
|
@ -121,19 +141,34 @@ function PickSubstitutions({
|
|||
setRightCst(undefined);
|
||||
}
|
||||
|
||||
const handleDeleteRow = useCallback(
|
||||
(row: number) => {
|
||||
const handleDeclineSuggestion = useCallback(
|
||||
(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 => {
|
||||
const newItems: ICstSubstitute[] = [];
|
||||
prev.forEach((item, index) => {
|
||||
if (index !== row) {
|
||||
prev.forEach(item => {
|
||||
if (item.original !== target.original.id || item.substitution !== target.substitution.id) {
|
||||
newItems.push(item);
|
||||
}
|
||||
});
|
||||
return newItems;
|
||||
});
|
||||
},
|
||||
[setSubstitutions]
|
||||
[setSubstitutions, handleDeclineSuggestion]
|
||||
);
|
||||
|
||||
const columns = useMemo(
|
||||
|
@ -169,19 +204,47 @@ function PickSubstitutions({
|
|||
}),
|
||||
columnHelper.display({
|
||||
id: 'actions',
|
||||
cell: props => (
|
||||
cell: props =>
|
||||
props.row.original.is_suggestion ? (
|
||||
<div className='max-w-fit'>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Принять предложение'
|
||||
icon={<IconAccept size='1rem' className='icon-green' />}
|
||||
onClick={() => handleAcceptSuggestion(props.row.original)}
|
||||
/>
|
||||
<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={() => handleDeleteRow(props.row.index)}
|
||||
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 (
|
||||
|
@ -265,6 +328,7 @@ function PickSubstitutions({
|
|||
<p>Добавьте отождествление</p>
|
||||
</NoData>
|
||||
}
|
||||
conditionalRowStyles={conditionalRowStyles}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
|
||||
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 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]
|
||||
);
|
||||
|
||||
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
|
||||
const [suggestions, setSuggestions] = useState<ICstSubstitute[]>([]);
|
||||
|
||||
const cache = useRSFormCache();
|
||||
const schemas = useMemo(
|
||||
() => 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]);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
cache.preload(schemasIDs);
|
||||
}, [schemasIDs]);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (cache.loading || schemas.length !== schemasIDs.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -86,13 +89,14 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
);
|
||||
}, [schemasIDs, schemas, cache.loading]);
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (cache.loading || schemas.length !== schemasIDs.length) {
|
||||
return;
|
||||
}
|
||||
const validator = new SubstitutionValidator(schemas, substitutions);
|
||||
setIsCorrect(validator.validate());
|
||||
setValidationText(validator.msg);
|
||||
setSuggestions(validator.suggestions);
|
||||
}, [substitutions, cache.loading, schemas, schemasIDs.length]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
@ -151,10 +155,11 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
isCorrect={isCorrect}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
suggestions={suggestions}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[cache.loading, cache.error, substitutions, schemas, validationText, isCorrect]
|
||||
[cache.loading, cache.error, substitutions, suggestions, schemas, validationText, isCorrect]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -16,6 +16,7 @@ interface TabSynthesisProps {
|
|||
schemas: IRSForm[];
|
||||
substitutions: ICstSubstitute[];
|
||||
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
|
||||
suggestions: ICstSubstitute[];
|
||||
}
|
||||
|
||||
function TabSynthesis({
|
||||
|
@ -25,7 +26,8 @@ function TabSynthesis({
|
|||
validationText,
|
||||
isCorrect,
|
||||
substitutions,
|
||||
setSubstitutions
|
||||
setSubstitutions,
|
||||
suggestions
|
||||
}: TabSynthesisProps) {
|
||||
const { colors } = useConceptOptions();
|
||||
return (
|
||||
|
@ -36,6 +38,7 @@ function TabSynthesis({
|
|||
rows={10}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
suggestions={suggestions}
|
||||
/>
|
||||
<TextArea
|
||||
disabled
|
||||
|
|
|
@ -133,6 +133,7 @@ export interface IMultiSubstitution {
|
|||
original: IConstituenta;
|
||||
substitution: IConstituenta;
|
||||
substitution_source: ILibraryItem;
|
||||
is_suggestion: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Graph } from './Graph';
|
|||
import { ILibraryItem, LibraryItemID } from './library';
|
||||
import { ICstSubstitute, IOperation, IOperationSchema, SubstitutionErrorType } from './oss';
|
||||
import { ConstituentaID, CstClass, CstType, IConstituenta, IRSForm } from './rsform';
|
||||
import { inferClass } from './rsformAPI';
|
||||
import { AliasMapping, ParsingStatus } from './rslang';
|
||||
import { applyAliasMapping, applyTypificationMapping, extractGlobals, isSetTypification } from './rslangAPI';
|
||||
|
||||
|
@ -59,6 +58,7 @@ type CrossMapping = Map<LibraryItemID, AliasMapping>;
|
|||
*/
|
||||
export class SubstitutionValidator {
|
||||
public msg: string = '';
|
||||
public suggestions: ICstSubstitute[] = [];
|
||||
|
||||
private schemas: IRSForm[];
|
||||
private substitutions: ICstSubstitute[];
|
||||
|
@ -102,6 +102,7 @@ export class SubstitutionValidator {
|
|||
}
|
||||
|
||||
public validate(): boolean {
|
||||
this.calculateSuggestions();
|
||||
if (this.substitutions.length === 0) {
|
||||
return this.setValid();
|
||||
}
|
||||
|
@ -118,6 +119,55 @@ export class SubstitutionValidator {
|
|||
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 {
|
||||
for (const item of this.substitutions) {
|
||||
const original = this.cstByID.get(item.original);
|
||||
|
@ -242,7 +292,7 @@ export class SubstitutionValidator {
|
|||
continue;
|
||||
}
|
||||
const substitution = this.cstByID.get(item.substitution)!;
|
||||
if (original.cst_type === substitution.cst_type && inferClass(original.cst_type) === CstClass.DERIVED) {
|
||||
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.
|
||||
|
@ -320,7 +370,6 @@ export class SubstitutionValidator {
|
|||
} else {
|
||||
substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!);
|
||||
substitutionText = applyTypificationMapping(substitutionText, result);
|
||||
console.log(substitutionText);
|
||||
if (!isSetTypification(substitutionText)) {
|
||||
this.reportError(SubstitutionErrorType.baseSubstitutionNotSet, [
|
||||
substitution.alias,
|
||||
|
|
|
@ -46,8 +46,9 @@ function HelpConceptOSS() {
|
|||
<p>
|
||||
Операция синтеза в рамках ОСС задаются набором операций-аргументов и <b>таблицей отождествлений</b> понятий из
|
||||
КС, привязанных к выбранным аргументам. Таким образом{' '}
|
||||
<LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на исходные
|
||||
(дописанные), наследованные, отождествленные (удаляемые).
|
||||
<LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на исходные и
|
||||
наследованные. При формировании таблицы отождествлений пользователю предлагается синтезировать производные
|
||||
понятия, выражения которых совпадают после проведения заданных отождествлений.
|
||||
</p>
|
||||
<p>
|
||||
После задания аргументов и таблицы отождествления необходимо единожды{' '}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { AnimatePresence } from 'framer-motion';
|
|||
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
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 MiniButton from '@/components/ui/MiniButton';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
|
@ -249,14 +249,14 @@ function FormConstituenta({
|
|||
<Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'>
|
||||
{state?.is_inherited_parent ? (
|
||||
<MiniButton
|
||||
icon={<IconChild size='1.25rem' className='clr-text-red' />}
|
||||
icon={<IconPredecessor size='1.25rem' className='clr-text-red' />}
|
||||
disabled
|
||||
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'
|
||||
/>
|
||||
) : null}
|
||||
{state?.is_inherited ? (
|
||||
<MiniButton
|
||||
icon={<IconParent size='1.25rem' className='clr-text-red' />}
|
||||
icon={<IconChild size='1.25rem' className='clr-text-red' />}
|
||||
disabled
|
||||
titleHtml='Внимание!</br> Конституента является наследником<br/>'
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue
Block a user