F: Implement UI for synthesis

This commit is contained in:
Ivan 2024-07-29 16:55:48 +03:00
parent 336b61957b
commit d3213211b5
20 changed files with 971 additions and 178 deletions

View File

@ -0,0 +1,273 @@
'use client';
import { useCallback, useMemo, useState } from 'react';
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { IBinarySubstitution, IConstituenta, IRSForm } from '@/models/rsform';
import { describeConstituenta } from '@/utils/labels';
import {
IconKeepAliasOff,
IconKeepAliasOn,
IconKeepTermOff,
IconKeepTermOn,
IconPageFirst,
IconPageLast,
IconPageLeft,
IconPageRight,
IconRemove,
IconReplace
} from '../Icons';
import NoData from '../ui/NoData';
interface PickInlineSubstitutionsProps {
prefixID: string;
rows?: number;
schema1?: IRSForm;
schema2?: IRSForm;
filter1?: (cst: IConstituenta) => boolean;
filter2?: (cst: IConstituenta) => boolean;
items: IBinarySubstitution[];
setItems: React.Dispatch<React.SetStateAction<IBinarySubstitution[]>>;
}
function SubstitutionIcon({ item }: { item: IBinarySubstitution }) {
if (item.deleteRight) {
if (item.takeLeftTerm) {
return <IconPageRight size='1.2rem' />;
} else {
return <IconPageLast size='1.2rem' />;
}
} else {
if (item.takeLeftTerm) {
return <IconPageFirst size='1.2rem' />;
} else {
return <IconPageLeft size='1.2rem' />;
}
}
}
const columnHelper = createColumnHelper<IBinarySubstitution>();
function PickInlineSubstitutions({
items,
schema1,
schema2,
filter1,
filter2,
rows,
setItems,
prefixID
}: PickInlineSubstitutionsProps) {
const { colors } = useConceptOptions();
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
const [deleteRight, setDeleteRight] = useState(true);
const [takeLeftTerm, setTakeLeftTerm] = useState(true);
const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev);
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
const newSubstitution: IBinarySubstitution = {
leftCst: leftCst,
rightCst: rightCst,
deleteRight: deleteRight,
takeLeftTerm: takeLeftTerm
};
setItems([
newSubstitution,
...items.filter(
item =>
(!item.deleteRight && item.leftCst.id !== leftCst.id) ||
(item.deleteRight && item.rightCst.id !== rightCst.id)
)
]);
}
const handleDeleteRow = useCallback(
(row: number) => {
setItems(prev => {
const newItems: IBinarySubstitution[] = [];
prev.forEach((item, index) => {
if (index !== row) {
newItems.push(item);
}
});
return newItems;
});
},
[setItems]
);
const columns = useMemo(
() => [
columnHelper.accessor(item => describeConstituenta(item.leftCst), {
id: 'left_text',
header: 'Описание',
size: 1000,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.accessor(item => item.leftCst.alias, {
id: 'left_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
cell: props => (
<BadgeConstituenta theme={colors} value={props.row.original.leftCst} prefixID={`${prefixID}_1_`} />
)
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: props => <SubstitutionIcon item={props.row.original} />
}),
columnHelper.accessor(item => item.rightCst.alias, {
id: 'right_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
cell: props => (
<BadgeConstituenta theme={colors} value={props.row.original.rightCst} prefixID={`${prefixID}_2_`} />
)
}),
columnHelper.accessor(item => describeConstituenta(item.rightCst), {
id: 'right_text',
header: 'Описание',
minSize: 1000,
cell: props => <div className='text-xs text-ellipsis text-pretty'>{props.getValue()}</div>
}),
columnHelper.display({
id: 'actions',
cell: props => (
<MiniButton
noHover
title='Удалить'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)}
/>
)
})
],
[handleDeleteRow, colors, prefixID]
);
return (
<div className='flex flex-col w-full'>
<div className='flex items-end gap-3 justify-stretch'>
<div className='flex-grow basis-1/2'>
<div className='flex items-center justify-between'>
<Label text={schema1 !== schema2 ? schema1?.alias ?? 'Схема 1' : ''} />
<div className='cc-icons'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
</div>
<SelectConstituenta
items={schema1?.items.filter(cst => !filter1 || filter1(cst))}
value={leftCst}
onSelectValue={setLeftCst}
/>
</div>
<MiniButton
noHover
title='Добавить в таблицу отождествлений'
className='mb-[0.375rem] grow-0'
icon={<IconReplace size='1.5rem' className='icon-primary' />}
disabled={!leftCst || !rightCst || leftCst === rightCst}
onClick={addSubstitution}
/>
<div className='flex-grow basis-1/2'>
<div className='flex items-center justify-between'>
<Label text={schema1 !== schema2 ? schema2?.alias ?? 'Схема 2' : ''} />
<div className='cc-icons'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
!deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
!takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
</div>
<SelectConstituenta
items={schema2?.items.filter(cst => !filter2 || filter2(cst))}
value={rightCst}
onSelectValue={setRightCst}
/>
</div>
</div>
<DataTable
dense
noHeader
noFooter
className='w-full text-sm border select-none cc-scroll-y'
rows={rows}
contentHeight='1.3rem'
data={items}
columns={columns}
headPosition='0'
noDataComponent={
<NoData className='min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте отождествление</p>
</NoData>
}
/>
</div>
);
}
export default PickInlineSubstitutions;

View File

@ -5,74 +5,100 @@ import { useCallback, useMemo, useState } from 'react';
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 } from '@/components/ui/DataTable';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { IConstituenta, IRSForm, ISingleSubstitution } from '@/models/rsform'; import { LibraryItemID } from '@/models/library';
import { describeConstituenta } from '@/utils/labels'; import { ICstSubstitute, IMultiSubstitution, IOperation } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { import {
IconKeepAliasOff, IconKeepAliasOff,
IconKeepAliasOn, IconKeepAliasOn,
IconKeepTermOff, IconKeepTermOff,
IconKeepTermOn, IconKeepTermOn,
IconPageFirst,
IconPageLast, IconPageLast,
IconPageLeft,
IconPageRight, IconPageRight,
IconRemove, IconRemove,
IconReplace IconReplace
} from '../Icons'; } from '../Icons';
import NoData from '../ui/NoData'; import NoData from '../ui/NoData';
import SelectOperation from './SelectOperation';
function SubstitutionIcon({ item, className }: { item: IMultiSubstitution; className?: string }) {
if (!item.transfer_term) {
return <IconPageRight size='1.2rem' className={className} />;
} else {
return <IconPageLast size='1.2rem' className={className} />;
}
}
interface PickSubstitutionsProps { interface PickSubstitutionsProps {
prefixID: string; prefixID: string;
rows?: number; rows?: number;
schema1?: IRSForm; operations: IOperation[];
schema2?: IRSForm; getSchema: (id: LibraryItemID) => IRSForm | undefined;
filter1?: (cst: IConstituenta) => boolean; getConstituenta: (id: ConstituentaID) => IConstituenta | undefined;
filter2?: (cst: IConstituenta) => boolean; getSchemaByCst: (id: ConstituentaID) => IRSForm | undefined;
substitutions: ICstSubstitute[];
items: ISingleSubstitution[]; setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
setItems: React.Dispatch<React.SetStateAction<ISingleSubstitution[]>>;
} }
function SubstitutionIcon({ item }: { item: ISingleSubstitution }) { const columnHelper = createColumnHelper<IMultiSubstitution>();
if (item.deleteRight) {
if (item.takeLeftTerm) {
return <IconPageRight size='1.2rem' />;
} else {
return <IconPageLast size='1.2rem' />;
}
} else {
if (item.takeLeftTerm) {
return <IconPageFirst size='1.2rem' />;
} else {
return <IconPageLeft size='1.2rem' />;
}
}
}
const columnHelper = createColumnHelper<ISingleSubstitution>();
function PickSubstitutions({ function PickSubstitutions({
items, prefixID,
schema1,
schema2,
filter1,
filter2,
rows, rows,
setItems, operations,
prefixID getSchema,
getConstituenta,
getSchemaByCst,
substitutions,
setSubstitutions
}: PickSubstitutionsProps) { }: PickSubstitutionsProps) {
const { colors } = useConceptOptions(); const { colors } = useConceptOptions();
const [leftArgument, setLeftArgument] = useState<IOperation | undefined>(undefined);
const [rightArgument, setRightArgument] = useState<IOperation | undefined>(undefined);
const leftSchema = useMemo(
() => (leftArgument?.result ? getSchema(leftArgument.result) : undefined),
[leftArgument, getSchema]
);
const rightSchema = useMemo(
() => (rightArgument?.result ? getSchema(rightArgument.result) : undefined),
[rightArgument, getSchema]
);
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined); const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined); const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
const [deleteRight, setDeleteRight] = useState(true); const [deleteRight, setDeleteRight] = useState(true);
const [takeLeftTerm, setTakeLeftTerm] = useState(true); const [takeLeftTerm, setTakeLeftTerm] = useState(true);
const operationByConstituenta = useCallback(
(cst: ConstituentaID): IOperation | undefined => {
const schema = getSchemaByCst(cst);
if (!schema) {
return undefined;
}
const cstOperations = operations.filter(item => item.result === schema.id);
return cstOperations.length === 1 ? cstOperations[0] : undefined;
},
[getSchemaByCst, operations]
);
const substitutionData: IMultiSubstitution[] = useMemo(
() =>
substitutions.map(item => ({
original_operation: operationByConstituenta(item.original),
original: getConstituenta(item.original),
substitution: getConstituenta(item.substitution),
substitution_operation: operationByConstituenta(item.substitution),
transfer_term: item.transfer_term
})),
[getConstituenta, operationByConstituenta, substitutions]
);
const toggleDelete = () => setDeleteRight(prev => !prev); const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev); const toggleTerm = () => setTakeLeftTerm(prev => !prev);
@ -80,26 +106,20 @@ function PickSubstitutions({
if (!leftCst || !rightCst) { if (!leftCst || !rightCst) {
return; return;
} }
const newSubstitution: ISingleSubstitution = { const newSubstitution: ICstSubstitute = {
leftCst: leftCst, original: deleteRight ? rightCst.id : leftCst.id,
rightCst: rightCst, substitution: deleteRight ? leftCst.id : rightCst.id,
deleteRight: deleteRight, transfer_term: deleteRight != takeLeftTerm
takeLeftTerm: takeLeftTerm
}; };
setItems([ setSubstitutions(prev => [...prev, newSubstitution]);
newSubstitution, setLeftCst(undefined);
...items.filter( setRightCst(undefined);
item =>
(!item.deleteRight && item.leftCst.id !== leftCst.id) ||
(item.deleteRight && item.rightCst.id !== rightCst.id)
)
]);
} }
const handleDeleteRow = useCallback( const handleDeleteRow = useCallback(
(row: number) => { (row: number) => {
setItems(prev => { setSubstitutions(prev => {
const newItems: ISingleSubstitution[] = []; const newItems: ICstSubstitute[] = [];
prev.forEach((item, index) => { prev.forEach((item, index) => {
if (index !== row) { if (index !== row) {
newItems.push(item); newItems.push(item);
@ -108,23 +128,26 @@ function PickSubstitutions({
return newItems; return newItems;
}); });
}, },
[setItems] [setSubstitutions]
); );
const columns = useMemo( const columns = useMemo(
() => [ () => [
columnHelper.accessor(item => describeConstituenta(item.leftCst), { columnHelper.accessor(item => item.substitution_operation?.alias ?? 'N/A', {
id: 'left_text', id: 'left_schema',
header: 'Описание', header: 'Операция',
size: 1000, size: 100,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div> cell: props => <div className='min-w-[10rem] text-ellipsis text-right'>{props.getValue()}</div>
}), }),
columnHelper.accessor(item => item.leftCst.alias, { columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', {
id: 'left_alias', id: 'left_alias',
header: () => <span className='pl-3'>Имя</span>, header: () => <span className='pl-3'>Имя</span>,
size: 65, size: 65,
cell: props => ( cell: props =>
<BadgeConstituenta theme={colors} value={props.row.original.leftCst} prefixID={`${prefixID}_1_`} /> props.row.original.substitution ? (
<BadgeConstituenta theme={colors} value={props.row.original.substitution} prefixID={`${prefixID}_1_`} />
) : (
'N/A'
) )
}), }),
columnHelper.display({ columnHelper.display({
@ -133,29 +156,34 @@ function PickSubstitutions({
size: 40, size: 40,
cell: props => <SubstitutionIcon item={props.row.original} /> cell: props => <SubstitutionIcon item={props.row.original} />
}), }),
columnHelper.accessor(item => item.rightCst.alias, { columnHelper.accessor(item => item.original?.alias ?? 'N/A', {
id: 'right_alias', id: 'right_alias',
header: () => <span className='pl-3'>Имя</span>, header: () => <span className='pl-3'>Имя</span>,
size: 65, size: 65,
cell: props => ( cell: props =>
<BadgeConstituenta theme={colors} value={props.row.original.rightCst} prefixID={`${prefixID}_2_`} /> props.row.original.original ? (
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_1_`} />
) : (
'N/A'
) )
}), }),
columnHelper.accessor(item => describeConstituenta(item.rightCst), { columnHelper.accessor(item => item.original_operation?.alias ?? 'N/A', {
id: 'right_text', id: 'right_schema',
header: 'Описание', header: 'Операция',
minSize: 1000, size: 100,
cell: props => <div className='text-xs text-ellipsis text-pretty'>{props.getValue()}</div> cell: props => <div className='min-w-[8rem] text-ellipsis'>{props.getValue()}</div>
}), }),
columnHelper.display({ columnHelper.display({
id: 'actions', id: 'actions',
cell: props => ( cell: props => (
<div className='max-w-fit'>
<MiniButton <MiniButton
noHover noHover
title='Удалить' title='Удалить'
icon={<IconRemove size='1rem' className='icon-red' />} icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)} onClick={() => handleDeleteRow(props.row.index)}
/> />
</div>
) )
}) })
], ],
@ -165,10 +193,8 @@ function PickSubstitutions({
return ( return (
<div className='flex flex-col w-full'> <div className='flex flex-col w-full'>
<div className='flex items-end gap-3 justify-stretch'> <div className='flex items-end gap-3 justify-stretch'>
<div className='flex-grow basis-1/2'> <div className='flex-grow flex flex-col basis-1/2'>
<div className='flex items-center justify-between'> <div className='cc-icons mb-1 w-fit mx-auto'>
<Label text={schema1 !== schema2 ? schema1?.alias ?? 'Схема 1' : ''} />
<div className='cc-icons'>
<MiniButton <MiniButton
title='Сохранить конституенту' title='Сохранить конституенту'
noHover noHover
@ -194,13 +220,21 @@ function PickSubstitutions({
} }
/> />
</div> </div>
</div> <div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectOperation
noBorder
items={operations.filter(item => item.id !== rightArgument?.id)}
value={leftArgument}
onSelectValue={setLeftArgument}
/>
<SelectConstituenta <SelectConstituenta
items={schema1?.items.filter(cst => !filter1 || filter1(cst))} noBorder
items={leftSchema?.items.filter(cst => !substitutions.find(item => item.original === cst.id))}
value={leftCst} value={leftCst}
onSelectValue={setLeftCst} onSelectValue={setLeftCst}
/> />
</div> </div>
</div>
<MiniButton <MiniButton
noHover noHover
@ -212,9 +246,7 @@ function PickSubstitutions({
/> />
<div className='flex-grow basis-1/2'> <div className='flex-grow basis-1/2'>
<div className='flex items-center justify-between'> <div className='cc-icons mb-1 w-fit mx-auto'>
<Label text={schema1 !== schema2 ? schema2?.alias ?? 'Схема 2' : ''} />
<div className='cc-icons'>
<MiniButton <MiniButton
title='Сохранить конституенту' title='Сохранить конституенту'
noHover noHover
@ -240,14 +272,22 @@ function PickSubstitutions({
} }
/> />
</div> </div>
</div> <div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectOperation
noBorder
items={operations.filter(item => item.id !== leftArgument?.id)}
value={rightArgument}
onSelectValue={setRightArgument}
/>
<SelectConstituenta <SelectConstituenta
items={schema2?.items.filter(cst => !filter2 || filter2(cst))} noBorder
items={rightSchema?.items.filter(cst => !substitutions.find(item => item.original === cst.id))}
value={rightCst} value={rightCst}
onSelectValue={setRightCst} onSelectValue={setRightCst}
/> />
</div> </div>
</div> </div>
</div>
<DataTable <DataTable
dense dense
@ -256,7 +296,7 @@ function PickSubstitutions({
className='w-full text-sm border select-none cc-scroll-y' className='w-full text-sm border select-none cc-scroll-y'
rows={rows} rows={rows}
contentHeight='1.3rem' contentHeight='1.3rem'
data={items} data={substitutionData}
columns={columns} columns={columns}
headPosition='0' headPosition='0'
noDataComponent={ noDataComponent={

View File

@ -49,7 +49,7 @@ function SelectConstituenta({
<SelectSingle <SelectSingle
className={clsx('text-ellipsis', className)} className={clsx('text-ellipsis', className)}
options={options} options={options}
value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : undefined} value={value ? { value: value.id, label: `${value.alias}: ${describeConstituentaTerm(value)}` } : null}
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))} onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object // @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter} filterOption={filter}

View File

@ -47,7 +47,7 @@ function SelectOperation({
<SelectSingle <SelectSingle
className={clsx('text-ellipsis', className)} className={clsx('text-ellipsis', className)}
options={options} options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : undefined} value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))} onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object // @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter} filterOption={filter}

View File

@ -49,7 +49,7 @@ function SelectUser({
<SelectSingle <SelectSingle
className={clsx('text-ellipsis', className)} className={clsx('text-ellipsis', className)}
options={options} options={options}
value={value ? { value: value, label: getUserLabel(value) } : undefined} value={value ? { value: value, label: getUserLabel(value) } : null}
onChange={data => { onChange={data => {
if (data !== null && data.value !== undefined) onSelectValue(data.value); if (data !== null && data.value !== undefined) onSelectValue(data.value);
}} }}

View File

@ -10,8 +10,8 @@ import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { HelpTopic, Position2D } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IOperationCreateData, IOperationPosition, IOperationSchema, OperationID, OperationType } from '@/models/oss'; import { IOperationCreateData, IOperationSchema, OperationID, OperationType } from '@/models/oss';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { describeOperationType, labelOperationType } from '@/utils/labels'; import { describeOperationType, labelOperationType } from '@/utils/labels';
@ -21,8 +21,6 @@ import TabSynthesisOperation from './TabSynthesisOperation';
interface DlgCreateOperationProps { interface DlgCreateOperationProps {
hideWindow: () => void; hideWindow: () => void;
oss: IOperationSchema; oss: IOperationSchema;
positions: IOperationPosition[];
insertPosition: Position2D;
onCreate: (data: IOperationCreateData) => void; onCreate: (data: IOperationCreateData) => void;
} }
@ -31,7 +29,7 @@ export enum TabID {
SYNTHESIS = 1 SYNTHESIS = 1
} }
function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCreate }: DlgCreateOperationProps) { function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationProps) {
const library = useLibrary(); const library = useLibrary();
const [activeTab, setActiveTab] = useState(TabID.INPUT); const [activeTab, setActiveTab] = useState(TabID.INPUT);
@ -62,8 +60,8 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
const handleSubmit = () => { const handleSubmit = () => {
const data: IOperationCreateData = { const data: IOperationCreateData = {
item_data: { item_data: {
position_x: insertPosition.x, position_x: 0,
position_y: insertPosition.y, position_y: 0,
alias: alias, alias: alias,
title: title, title: title,
comment: comment, comment: comment,
@ -71,7 +69,7 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
operation_type: activeTab === TabID.INPUT ? OperationType.INPUT : OperationType.SYNTHESIS, operation_type: activeTab === TabID.INPUT ? OperationType.INPUT : OperationType.SYNTHESIS,
result: activeTab === TabID.INPUT ? attachedID ?? null : null result: activeTab === TabID.INPUT ? attachedID ?? null : null
}, },
positions: positions, positions: [],
arguments: activeTab === TabID.INPUT ? undefined : inputs.length > 0 ? inputs : undefined, arguments: activeTab === TabID.INPUT ? undefined : inputs.length > 0 ? inputs : undefined,
create_schema: createSchema create_schema: createSchema
}; };

View File

@ -0,0 +1,172 @@
'use client';
import clsx from 'clsx';
import { useEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs';
import BadgeHelp from '@/components/info/BadgeHelp';
import Modal from '@/components/ui/Modal';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel';
import useRSFormCache from '@/hooks/useRSFormCache';
import { HelpTopic } from '@/models/miscellaneous';
import { ICstSubstitute, IOperation, IOperationSchema, OperationID, OperationType } from '@/models/oss';
import { PARAMETER } from '@/utils/constants';
import TabArguments from './TabArguments';
import TabOperation from './TabOperation';
import TabSynthesis from './TabSynthesis';
interface DlgEditOperationProps {
hideWindow: () => void;
oss: IOperationSchema;
target: IOperation;
// onSubmit: (data: IOperationEditData) => void;
onSubmit: () => void;
}
export enum TabID {
CARD = 0,
ARGUMENTS = 1,
SUBSTITUTION = 2
}
function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperationProps) {
const [activeTab, setActiveTab] = useState(TabID.CARD);
const [alias, setAlias] = useState(target.alias);
const [title, setTitle] = useState(target.title);
const [comment, setComment] = useState(target.comment);
const [syncText, setSyncText] = useState(true);
const [inputs, setInputs] = useState<OperationID[]>(oss.graph.expandInputs([target.id]));
const inputOperations = useMemo(() => inputs.map(id => oss.operationByID.get(id)!), [inputs, oss.operationByID]);
const schemasIDs = useMemo(
() => inputOperations.map(operation => operation.result).filter(id => id !== null),
[inputOperations]
);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(oss.substitutions);
const cache = useRSFormCache();
const isValid = useMemo(() => alias !== '', [alias]);
useEffect(() => {
cache.preload(schemasIDs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [schemasIDs]);
const handleSubmit = () => {
// const data: IOperationCreateData = {
// item_data: {
// position_x: insertPosition.x,
// position_y: insertPosition.y,
// alias: alias,
// title: title,
// comment: comment,
// sync_text: activeTab === TabID.INPUT ? syncText : true,
// operation_type: activeTab === TabID.INPUT ? OperationType.INPUT : OperationType.SYNTHESIS,
// result: activeTab === TabID.INPUT ? attachedID ?? null : null
// },
// positions: positions,
// arguments: activeTab === TabID.INPUT ? undefined : inputs.length > 0 ? inputs : undefined,
// create_schema: createSchema
// };
onSubmit();
};
const cardPanel = useMemo(
() => (
<TabPanel>
<TabOperation
alias={alias}
setAlias={setAlias}
comment={comment}
setComment={setComment}
title={title}
setTitle={setTitle}
syncText={syncText}
setSyncText={setSyncText}
/>
</TabPanel>
),
[alias, comment, title, syncText]
);
const argumentsPanel = useMemo(
() => (
<TabPanel>
<TabArguments
target={target.id} // prettier: split-lines
oss={oss}
inputs={inputs}
setInputs={setInputs}
/>
</TabPanel>
),
[oss, target, inputs]
);
const synthesisPanel = useMemo(
() => (
<TabPanel>
<TabSynthesis
operations={inputOperations}
loading={cache.loading}
error={cache.error}
getSchema={cache.getSchema}
getConstituenta={cache.getConstituenta}
getSchemaByCst={cache.getSchemaByCst}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>
</TabPanel>
),
[
inputOperations,
cache.loading,
cache.error,
cache.getSchema,
cache.getConstituenta,
substitutions,
cache.getSchemaByCst
]
);
return (
<Modal
header='Редактирование операции'
submitText='Сохранить'
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]'
>
<Overlay position='top-0 right-0'>
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />
</Overlay>
<Tabs
selectedTabClassName='clr-selected'
className='flex flex-col'
selectedIndex={activeTab}
onSelect={setActiveTab}
>
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<TabLabel title='Текстовые поля' label='Карточка' className='w-[8rem]' />
{target.operation_type === OperationType.SYNTHESIS ? (
<TabLabel title='Выбор аргументов операции' label='Аргументы' className='w-[8rem]' />
) : null}
{target.operation_type === OperationType.SYNTHESIS ? (
<TabLabel title='Таблица отождествлений' label='Отождествления' className='w-[8rem]' />
) : null}
</TabList>
{cardPanel}
{target.operation_type === OperationType.SYNTHESIS ? argumentsPanel : null}
{target.operation_type === OperationType.SYNTHESIS ? synthesisPanel : null}
</Tabs>
</Modal>
);
}
export default DlgEditOperation;

View File

@ -0,0 +1,35 @@
'use client';
import { useMemo } from 'react';
import FlexColumn from '@/components/ui/FlexColumn';
import Label from '@/components/ui/Label';
import AnimateFade from '@/components/wrap/AnimateFade';
import { IOperationSchema, OperationID } from '@/models/oss';
import PickMultiOperation from '../../components/select/PickMultiOperation';
interface TabArgumentsProps {
oss: IOperationSchema;
target: OperationID;
inputs: OperationID[];
setInputs: React.Dispatch<React.SetStateAction<OperationID[]>>;
}
function TabArguments({ oss, inputs, target, setInputs }: TabArgumentsProps) {
const potentialCycle = useMemo(() => [target, ...oss.graph.expandAllOutputs([target])], [target, oss.graph]);
const filtered = useMemo(
() => oss.items.filter(item => !potentialCycle.includes(item.id)),
[oss.items, potentialCycle]
);
return (
<AnimateFade className='cc-column'>
<FlexColumn>
<Label text={`Выбор аргументов: [ ${inputs.length} ]`} />
<PickMultiOperation items={filtered} selected={inputs} setSelected={setInputs} rows={8} />
</FlexColumn>
</AnimateFade>
);
}
export default TabArguments;

View File

@ -0,0 +1,69 @@
import Checkbox from '@/components/ui/Checkbox';
import FlexColumn from '@/components/ui/FlexColumn';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
import { limits, patterns } from '@/utils/constants';
interface TabOperationProps {
alias: string;
setAlias: React.Dispatch<React.SetStateAction<string>>;
title: string;
setTitle: React.Dispatch<React.SetStateAction<string>>;
comment: string;
setComment: React.Dispatch<React.SetStateAction<string>>;
syncText: boolean;
setSyncText: React.Dispatch<React.SetStateAction<boolean>>;
}
function TabOperation({
alias,
setAlias,
title,
setTitle,
comment,
setComment,
syncText,
setSyncText
}: TabOperationProps) {
return (
<AnimateFade className='cc-column'>
<TextInput
id='operation_title'
label='Полное название'
value={title}
onChange={event => setTitle(event.target.value)}
/>
<div className='flex gap-6'>
<FlexColumn>
<TextInput
id='operation_alias'
label='Сокращение'
className='w-[14rem]'
pattern={patterns.library_alias}
title={`не более ${limits.library_alias_len} символов`}
value={alias}
onChange={event => setAlias(event.target.value)}
/>
<Checkbox
value={syncText}
setValue={setSyncText}
label='Синхронизировать текст'
titleHtml='Загрузить текстовые поля<br/> из концептуальной схемы'
/>
</FlexColumn>
<TextArea
id='operation_comment'
label='Описание'
noResize
rows={3}
value={comment}
onChange={event => setComment(event.target.value)}
/>
</div>
</AnimateFade>
);
}
export default TabOperation;

View File

@ -0,0 +1,47 @@
import { ErrorData } from '@/components/info/InfoError';
import PickSubstitutions from '@/components/select/PickSubstitutions';
import DataLoader from '@/components/wrap/DataLoader';
import { LibraryItemID } from '@/models/library';
import { ICstSubstitute, IOperation } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
interface TabSynthesisProps {
loading: boolean;
error: ErrorData;
operations: IOperation[];
getSchema: (id: LibraryItemID) => IRSForm | undefined;
getConstituenta: (id: ConstituentaID) => IConstituenta | undefined;
getSchemaByCst: (id: ConstituentaID) => IRSForm | undefined;
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
}
function TabSynthesis({
operations,
loading,
error,
getSchema,
getConstituenta,
getSchemaByCst,
substitutions,
setSubstitutions
}: TabSynthesisProps) {
return (
<DataLoader id='dlg-synthesis-tab' className='cc-column' isLoading={loading} error={error}>
<PickSubstitutions
prefixID={prefixes.dlg_cst_substitutes_list}
rows={8}
operations={operations}
getSchema={getSchema}
getConstituenta={getConstituenta}
getSchemaByCst={getSchemaByCst}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>
</DataLoader>
);
}
export default TabSynthesis;

View File

@ -0,0 +1 @@
export { default } from './DlgEditOperation';

View File

@ -8,7 +8,7 @@ import Modal, { ModalProps } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import useRSFormDetails from '@/hooks/useRSFormDetails'; import useRSFormDetails from '@/hooks/useRSFormDetails';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { IInlineSynthesisData, IRSForm, ISingleSubstitution } from '@/models/rsform'; import { IBinarySubstitution, IInlineSynthesisData, IRSForm } from '@/models/rsform';
import TabConstituents from './TabConstituents'; import TabConstituents from './TabConstituents';
import TabSchema from './TabSchema'; import TabSchema from './TabSchema';
@ -30,7 +30,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined); const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
const [selected, setSelected] = useState<LibraryItemID[]>([]); const [selected, setSelected] = useState<LibraryItemID[]>([]);
const [substitutions, setSubstitutions] = useState<ISingleSubstitution[]>([]); const [substitutions, setSubstitutions] = useState<IBinarySubstitution[]>([]);
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined }); const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });

View File

@ -2,10 +2,10 @@
import { ErrorData } from '@/components/info/InfoError'; import { ErrorData } from '@/components/info/InfoError';
import DataLoader from '@/components/wrap/DataLoader'; import DataLoader from '@/components/wrap/DataLoader';
import { ConstituentaID, IRSForm, ISingleSubstitution } from '@/models/rsform'; import { ConstituentaID, IBinarySubstitution, IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import PickSubstitutions from '../../components/select/PickSubstitutions'; import PickInlineSubstitutions from '../../components/select/PickInlineSubstitutions';
interface TabSubstitutionsProps { interface TabSubstitutionsProps {
receiver?: IRSForm; receiver?: IRSForm;
@ -15,8 +15,8 @@ interface TabSubstitutionsProps {
loading?: boolean; loading?: boolean;
error?: ErrorData; error?: ErrorData;
substitutions: ISingleSubstitution[]; substitutions: IBinarySubstitution[];
setSubstitutions: React.Dispatch<React.SetStateAction<ISingleSubstitution[]>>; setSubstitutions: React.Dispatch<React.SetStateAction<IBinarySubstitution[]>>;
} }
function TabSubstitutions({ function TabSubstitutions({
@ -32,7 +32,7 @@ function TabSubstitutions({
}: TabSubstitutionsProps) { }: TabSubstitutionsProps) {
return ( return (
<DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={loading} error={error} hasNoData={!source}> <DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={loading} error={error} hasNoData={!source}>
<PickSubstitutions <PickInlineSubstitutions
items={substitutions} items={substitutions}
setItems={setSubstitutions} setItems={setSubstitutions}
rows={10} rows={10}

View File

@ -3,11 +3,11 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import PickSubstitutions from '@/components/select/PickSubstitutions'; import PickInlineSubstitutions from '@/components/select/PickInlineSubstitutions';
import Modal, { ModalProps } from '@/components/ui/Modal'; import Modal, { ModalProps } from '@/components/ui/Modal';
import { useRSForm } from '@/context/RSFormContext'; import { useRSForm } from '@/context/RSFormContext';
import { ICstSubstituteData } from '@/models/oss'; import { ICstSubstituteData } from '@/models/oss';
import { ISingleSubstitution } from '@/models/rsform'; import { IBinarySubstitution } from '@/models/rsform';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> { interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
@ -17,7 +17,7 @@ interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) { function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
const { schema } = useRSForm(); const { schema } = useRSForm();
const [substitutions, setSubstitutions] = useState<ISingleSubstitution[]>([]); const [substitutions, setSubstitutions] = useState<IBinarySubstitution[]>([]);
const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]); const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]);
@ -42,7 +42,7 @@ function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
onSubmit={handleSubmit} onSubmit={handleSubmit}
className={clsx('w-[40rem]', 'px-6 pb-3')} className={clsx('w-[40rem]', 'px-6 pb-3')}
> >
<PickSubstitutions <PickInlineSubstitutions
items={substitutions} items={substitutions}
setItems={setSubstitutions} setItems={setSubstitutions}
rows={6} rows={6}

View File

@ -0,0 +1,82 @@
'use client';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { getRSFormDetails } from '@/backend/rsforms';
import { type ErrorData } from '@/components/info/InfoError';
import { LibraryItemID } from '@/models/library';
import { ConstituentaID, IRSForm, IRSFormData } from '@/models/rsform';
import { RSFormLoader } from '@/models/RSFormLoader';
function useRSFormCache() {
const [cache, setCache] = useState<IRSForm[]>([]);
const [pending, setPending] = useState<LibraryItemID[]>([]);
const [processing, setProcessing] = useState<LibraryItemID[]>([]);
const loading = useMemo(() => pending.length > 0 || processing.length > 0, [pending, processing]);
const [error, setError] = useState<ErrorData>(undefined);
function setSchema(data: IRSFormData) {
const schema = new RSFormLoader(data).produceRSForm();
setCache(prev => [...prev, schema]);
}
const getSchema = useCallback((id: LibraryItemID) => cache.find(item => item.id === id), [cache]);
const getSchemaByCst = useCallback(
(id: ConstituentaID) => {
for (const schema of cache) {
const cst = schema.items.find(cst => cst.id === id);
if (cst) {
return schema;
}
}
return undefined;
},
[cache]
);
const getConstituenta = useCallback(
(id: ConstituentaID) => {
for (const schema of cache) {
const cst = schema.items.find(cst => cst.id === id);
if (cst) {
return cst;
}
}
return undefined;
},
[cache]
);
const preload = useCallback(
(target: LibraryItemID[]) => setPending(prev => [...prev, ...target.filter(id => !prev.includes(id))]),
[]
);
useEffect(() => {
const ids = pending.filter(id => !processing.includes(id) && !cache.find(schema => schema.id === id));
if (ids.length === 0) {
return;
}
setProcessing(prev => [...prev, ...ids]);
setPending([]);
ids.forEach(id =>
getRSFormDetails(String(id), '', {
showError: false,
onError: error => {
setProcessing(prev => prev.filter(item => item !== id));
setError(error);
},
onSuccess: data => {
setProcessing(prev => prev.filter(item => item !== id));
setSchema(data);
}
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pending]);
return { preload, getSchema, getConstituenta, getSchemaByCst, loading, error, setError };
}
export default useRSFormCache;

View File

@ -4,7 +4,7 @@
import { Graph } from './Graph'; import { Graph } from './Graph';
import { ILibraryItem, ILibraryItemData, LibraryItemID } from './library'; import { ILibraryItem, ILibraryItemData, LibraryItemID } from './library';
import { ConstituentaID } from './rsform'; import { ConstituentaID, IConstituenta } from './rsform';
/** /**
* Represents {@link IOperation} identifier type. * Represents {@link IOperation} identifier type.
@ -101,6 +101,17 @@ export interface ICstSubstituteData {
substitutions: ICstSubstitute[]; substitutions: ICstSubstitute[];
} }
/**
* Represents substitution for multi synthesis table.
*/
export interface IMultiSubstitution {
original_operation: IOperation | undefined;
original: IConstituenta | undefined;
substitution: IConstituenta | undefined;
substitution_operation: IOperation | undefined;
transfer_term: boolean;
}
/** /**
* Represents {@link ICstSubstitute} extended data. * Represents {@link ICstSubstitute} extended data.
*/ */

View File

@ -241,9 +241,9 @@ export interface IVersionCreatedResponse {
} }
/** /**
* Represents single substitution for synthesis table. * Represents single substitution for binary synthesis table.
*/ */
export interface ISingleSubstitution { export interface IBinarySubstitution {
leftCst: IConstituenta; leftCst: IConstituenta;
rightCst: IConstituenta; rightCst: IConstituenta;
deleteRight: boolean; deleteRight: boolean;

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { IconConnect, IconDestroy, IconEdit2, IconExecute, IconNewItem, IconRSForm } from '@/components/Icons'; import { IconConnect, IconDestroy, IconEdit2, IconExecute, IconNewItem, IconRSForm } from '@/components/Icons';
@ -24,6 +24,7 @@ interface NodeContextMenuProps extends ContextMenuData {
onDelete: (target: OperationID) => void; onDelete: (target: OperationID) => void;
onCreateInput: (target: OperationID) => void; onCreateInput: (target: OperationID) => void;
onEditSchema: (target: OperationID) => void; onEditSchema: (target: OperationID) => void;
onEditOperation: (target: OperationID) => void;
} }
function NodeContextMenu({ function NodeContextMenu({
@ -33,11 +34,32 @@ function NodeContextMenu({
onHide, onHide,
onDelete, onDelete,
onCreateInput, onCreateInput,
onEditSchema onEditSchema,
onEditOperation
}: NodeContextMenuProps) { }: NodeContextMenuProps) {
const controller = useOssEdit(); const controller = useOssEdit();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null); const ref = useRef(null);
const readyForSynthesis = useMemo(() => {
if (operation.operation_type !== OperationType.SYNTHESIS) {
return false;
}
if (!controller.schema || operation.result) {
return false;
}
const argumentIDs = controller.schema.graph.expandInputs([operation.id]);
if (!argumentIDs || argumentIDs.length < 2) {
return false;
}
const argumentOperations = argumentIDs.map(id => controller.schema!.operationByID.get(id)!);
if (argumentOperations.some(item => item.result === null)) {
return false;
}
return true;
}, [operation, controller.schema]);
const handleHide = useCallback(() => { const handleHide = useCallback(() => {
setIsOpen(false); setIsOpen(false);
@ -58,8 +80,8 @@ function NodeContextMenu({
}; };
const handleEditOperation = () => { const handleEditOperation = () => {
toast.error('Not implemented');
handleHide(); handleHide();
onEditOperation(operation.id);
}; };
const handleDeleteOperation = () => { const handleDeleteOperation = () => {
@ -118,9 +140,13 @@ function NodeContextMenu({
{controller.isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? ( {controller.isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? (
<DropdownButton <DropdownButton
text='Выполнить синтез' text='Выполнить синтез'
title='Выполнить операцию и получить синтезированную КС' title={
readyForSynthesis
? 'Выполнить операцию и получить синтезированную КС'
: 'Необходимо предоставить все аргументы'
}
icon={<IconExecute size='1rem' className='icon-green' />} icon={<IconExecute size='1rem' className='icon-green' />}
disabled={controller.isProcessing} disabled={controller.isProcessing || !readyForSynthesis}
onClick={handleRunSynthesis} onClick={handleRunSynthesis}
/> />
) : null} ) : null}

View File

@ -155,6 +155,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
[controller, getPositions] [controller, getPositions]
); );
const handleEditOperation = useCallback(
(target: OperationID) => {
controller.promptEditOperation(target, getPositions());
},
[controller, getPositions]
);
const handleFitView = useCallback(() => { const handleFitView = useCallback(() => {
flow.fitView({ duration: PARAMETER.zoomDuration }); flow.fitView({ duration: PARAMETER.zoomDuration });
}, [flow]); }, [flow]);
@ -301,6 +308,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
onDelete={handleDeleteOperation} onDelete={handleDeleteOperation}
onCreateInput={handleCreateInput} onCreateInput={handleCreateInput}
onEditSchema={handleEditSchema} onEditSchema={handleEditSchema}
onEditOperation={handleEditOperation}
{...menuProps} {...menuProps}
/> />
) : null} ) : null}

View File

@ -14,6 +14,7 @@ import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
import DlgChangeLocation from '@/dialogs/DlgChangeLocation'; import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
import DlgCreateOperation from '@/dialogs/DlgCreateOperation'; import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
import DlgEditEditors from '@/dialogs/DlgEditEditors'; import DlgEditEditors from '@/dialogs/DlgEditEditors';
import DlgEditOperation from '@/dialogs/DlgEditOperation';
import { AccessPolicy, LibraryItemID } from '@/models/library'; import { AccessPolicy, LibraryItemID } from '@/models/library';
import { Position2D } from '@/models/miscellaneous'; import { Position2D } from '@/models/miscellaneous';
import { import {
@ -53,6 +54,7 @@ export interface IOssEditContext {
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void; deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
createInput: (target: OperationID, positions: IOperationPosition[]) => void; createInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
} }
const OssEditContext = createContext<IOssEditContext | null>(null); const OssEditContext = createContext<IOssEditContext | null>(null);
@ -88,6 +90,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const [showEditEditors, setShowEditEditors] = useState(false); const [showEditEditors, setShowEditEditors] = useState(false);
const [showEditLocation, setShowEditLocation] = useState(false); const [showEditLocation, setShowEditLocation] = useState(false);
const [showEditInput, setShowEditInput] = useState(false); const [showEditInput, setShowEditInput] = useState(false);
const [showEditOperation, setShowEditOperation] = useState(false);
const [showCreateOperation, setShowCreateOperation] = useState(false); const [showCreateOperation, setShowCreateOperation] = useState(false);
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 }); const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
@ -211,11 +214,32 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const handleCreateOperation = useCallback( const handleCreateOperation = useCallback(
(data: IOperationCreateData) => { (data: IOperationCreateData) => {
data.positions = positions;
data.item_data.position_x = insertPosition.x;
data.item_data.position_y = insertPosition.y;
model.createOperation(data, operation => toast.success(information.newOperation(operation.alias))); model.createOperation(data, operation => toast.success(information.newOperation(operation.alias)));
}, },
[model] [model, positions, insertPosition]
); );
const promptEditOperation = useCallback((target: OperationID, positions: IOperationPosition[]) => {
setPositions(positions);
setTargetOperationID(target);
setShowEditOperation(true);
}, []);
const handleEditOperation = useCallback(() => {
// TODO: проверить наличие всех аргументов
// TODO: проверить наличие синтеза
// TODO: проверить полноту синтеза
// TODO: проверить правильность синтеза
// TODO: сохранить позиции
// TODO: обновить схему
// model.setInput(data, () => toast.success(information.changesSaved));
toast.error('Not implemented');
}, []);
const deleteOperation = useCallback( const deleteOperation = useCallback(
(target: OperationID, positions: IOperationPosition[]) => { (target: OperationID, positions: IOperationPosition[]) => {
model.deleteOperation({ target: target, positions: positions }, () => model.deleteOperation({ target: target, positions: positions }, () =>
@ -283,7 +307,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
promptCreateOperation, promptCreateOperation,
deleteOperation, deleteOperation,
createInput, createInput,
promptEditInput promptEditInput,
promptEditOperation
}} }}
> >
{model.schema ? ( {model.schema ? (
@ -306,8 +331,6 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
<DlgCreateOperation <DlgCreateOperation
hideWindow={() => setShowCreateOperation(false)} hideWindow={() => setShowCreateOperation(false)}
oss={model.schema} oss={model.schema}
positions={positions}
insertPosition={insertPosition}
onCreate={handleCreateOperation} onCreate={handleCreateOperation}
/> />
) : null} ) : null}
@ -319,6 +342,14 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
onSubmit={setTargetInput} onSubmit={setTargetInput}
/> />
) : null} ) : null}
{showEditOperation ? (
<DlgEditOperation
hideWindow={() => setShowEditOperation(false)}
oss={model.schema}
target={targetOperation!}
onSubmit={handleEditOperation}
/>
) : null}
</AnimatePresence> </AnimatePresence>
) : null} ) : null}