Compare commits

...

3 Commits

Author SHA1 Message Date
Ivan
3b036f2c9d Merge branch 'main' of http://dev.concept.ru:3000/ConceptProd/Portal
Some checks failed
Frontend CI / build (22.x) (push) Has been cancelled
2024-10-23 15:20:52 +03:00
Ivan
1da81dbfc1 F: Constituenta relocation pt1 2024-10-23 15:18:46 +03:00
Ivan
b1bfbe26cb M: Update dependencies + small fixes 2024-10-17 17:02:30 +03:00
14 changed files with 292 additions and 47 deletions

View File

@ -88,6 +88,8 @@ This readme file is used mostly to document project dependencies and conventions
- Rubik - Rubik
- Alegreya Sans SC - Alegreya Sans SC
- Noto Sans Math - Noto Sans Math
- Noto Sans Symbol
- Noto Color Emoji
</pre> </pre>
</details> </details>

View File

@ -4,6 +4,7 @@ import { IConstituenta } from '@/models/rsform';
import { isBasicConcept } from '@/models/rsformAPI'; import { isBasicConcept } from '@/models/rsformAPI';
import { labelCstTypification } from '@/utils/labels'; import { labelCstTypification } from '@/utils/labels';
import { IconChild } from '../Icons';
import { CProps } from '../props'; import { CProps } from '../props';
interface InfoConstituentaProps extends CProps.Div { interface InfoConstituentaProps extends CProps.Div {
@ -13,9 +14,11 @@ interface InfoConstituentaProps extends CProps.Div {
function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) { function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) {
return ( return (
<div className={clsx('dense min-w-[15rem] break-words', className)} {...restProps}> <div className={clsx('dense min-w-[15rem] break-words', className)} {...restProps}>
<h2> <h2 className='cursor-default' title={data.is_inherited ? ' наследник' : undefined}>
{data.alias} {data.alias}
{data.is_inherited ? ' (наследуется)' : ''} {data.is_inherited ? (
<IconChild size='1rem' className='inline-icon translate-y-[-0.1rem] translate-x-[0.125rem]' />
) : null}
</h2> </h2>
{data.term_resolved ? ( {data.term_resolved ? (
<p> <p>

View File

@ -5,6 +5,7 @@ import { useLayoutEffect, useMemo, useState } from 'react';
import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable'; import DataTable, { createColumnHelper, RowSelectionState } from '@/components/ui/DataTable';
import { useConceptOptions } from '@/context/ConceptOptionsContext'; import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { Graph } from '@/models/Graph';
import { CstMatchMode } from '@/models/miscellaneous'; import { CstMatchMode } from '@/models/miscellaneous';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform'; import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { isBasicConcept, matchConstituenta } from '@/models/rsformAPI'; import { isBasicConcept, matchConstituenta } from '@/models/rsformAPI';
@ -17,7 +18,9 @@ import ToolbarGraphSelection from './ToolbarGraphSelection';
interface PickMultiConstituentaProps { interface PickMultiConstituentaProps {
id?: string; id?: string;
schema?: IRSForm; schema: IRSForm;
data: IConstituenta[];
prefixID: string; prefixID: string;
rows?: number; rows?: number;
@ -27,12 +30,39 @@ interface PickMultiConstituentaProps {
const columnHelper = createColumnHelper<IConstituenta>(); const columnHelper = createColumnHelper<IConstituenta>();
function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelected }: PickMultiConstituentaProps) { function PickMultiConstituenta({
id,
schema,
data,
prefixID,
rows,
selected,
setSelected
}: PickMultiConstituentaProps) {
const { colors } = useConceptOptions(); const { colors } = useConceptOptions();
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}); const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
const [filtered, setFiltered] = useState<IConstituenta[]>(schema?.items ?? []); const [filtered, setFiltered] = useState<IConstituenta[]>(data);
const [filterText, setFilterText] = useState(''); const [filterText, setFilterText] = useState('');
const foldedGraph = useMemo(() => {
if (data.length === schema.items.length) {
return schema.graph;
}
const newGraph = new Graph();
schema.graph.nodes.forEach(node => {
newGraph.addNode(node.id);
node.outputs.forEach(output => {
newGraph.addEdge(node.id, output);
});
});
schema.items
.filter(item => data.find(cst => cst.id === item.id) === undefined)
.forEach(item => {
newGraph.foldNode(item.id);
});
return newGraph;
}, [schema.graph, data]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (filtered.length === 0) { if (filtered.length === 0) {
setRowSelection({}); setRowSelection({});
@ -46,17 +76,17 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
}, [filtered, setRowSelection, selected]); }, [filtered, setRowSelection, selected]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (!schema || schema.items.length === 0) { if (data.length === 0) {
setFiltered([]); setFiltered([]);
} else if (filterText) { } else if (filterText) {
setFiltered(schema.items.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL))); setFiltered(data.filter(cst => matchConstituenta(cst, filterText, CstMatchMode.ALL)));
} else { } else {
setFiltered(schema.items); setFiltered(data);
} }
}, [filterText, schema?.items, schema]); }, [filterText, data]);
function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) { function handleRowSelection(updater: React.SetStateAction<RowSelectionState>) {
if (!schema) { if (!data) {
setSelected([]); setSelected([]);
} else { } else {
const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater; const newRowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
@ -91,7 +121,7 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
<div> <div>
<div className='flex justify-between items-center clr-input px-3 border-x border-t rounded-t-md'> <div className='flex justify-between items-center clr-input px-3 border-x border-t rounded-t-md'>
<div className='w-[24ch] select-none whitespace-nowrap'> <div className='w-[24ch] select-none whitespace-nowrap'>
Выбраны {selected.length} из {schema?.items.length ?? 0} Выбраны {selected.length} из {data.length}
</div> </div>
<SearchBar <SearchBar
id='dlg_constituents_search' id='dlg_constituents_search'
@ -100,16 +130,14 @@ function PickMultiConstituenta({ id, schema, prefixID, rows, selected, setSelect
value={filterText} value={filterText}
onChange={setFilterText} onChange={setFilterText}
/> />
{schema ? ( <ToolbarGraphSelection
<ToolbarGraphSelection graph={foldedGraph}
graph={schema.graph} isCore={cstID => isBasicConcept(schema.cstByID.get(cstID)?.cst_type)}
isCore={cstID => isBasicConcept(schema.cstByID.get(cstID)?.cst_type)} isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited} setSelected={setSelected}
setSelected={setSelected} emptySelection={selected.length === 0}
emptySelection={selected.length === 0} className='w-fit'
className='w-fit' />
/>
) : null}
</div> </div>
<DataTable <DataTable
id={id} id={id}

View File

@ -9,7 +9,7 @@ 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 { ICstSubstitute } from '@/models/oss'; import { ICstSubstitute } from '@/models/oss';
import { IInlineSynthesisData, IRSForm } from '@/models/rsform'; import { ConstituentaID, 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 [activeTab, setActiveTab] = useState(TabID.SCHEMA); const [activeTab, setActiveTab] = useState(TabID.SCHEMA);
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined); const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
const [selected, setSelected] = useState<LibraryItemID[]>([]); const [selected, setSelected] = useState<ConstituentaID[]>([]);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]); const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]);
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined }); const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });

View File

@ -17,13 +17,16 @@ interface TabConstituentsProps {
function TabConstituents({ schema, error, loading, selected, setSelected }: TabConstituentsProps) { function TabConstituents({ schema, error, loading, selected, setSelected }: TabConstituentsProps) {
return ( return (
<DataLoader id='dlg-constituents-tab' isLoading={loading} error={error} hasNoData={!schema}> <DataLoader id='dlg-constituents-tab' isLoading={loading} error={error} hasNoData={!schema}>
<PickMultiConstituenta {schema ? (
schema={schema} <PickMultiConstituenta
rows={13} schema={schema}
prefixID={prefixes.cst_inline_synth_list} data={schema.items}
selected={selected} rows={13}
setSelected={setSelected} prefixID={prefixes.cst_inline_synth_list}
/> selected={selected}
setSelected={setSelected}
/>
) : null}
</DataLoader> </DataLoader>
); );
} }

View File

@ -0,0 +1,96 @@
'use client';
import clsx from 'clsx';
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
import SelectLibraryItem from '@/components/select/SelectLibraryItem';
import Modal, { ModalProps } from '@/components/ui/Modal';
import DataLoader from '@/components/wrap/DataLoader';
import { useLibrary } from '@/context/LibraryContext';
import useRSFormDetails from '@/hooks/useRSFormDetails';
import { ILibraryItem, LibraryItemID } from '@/models/library';
import { ICstRelocateData, IOperation, IOperationSchema } from '@/models/oss';
import { getRelocateCandidates } from '@/models/ossAPI';
import { ConstituentaID } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
interface DlgRelocateConstituentsProps extends Pick<ModalProps, 'hideWindow'> {
oss: IOperationSchema;
target: IOperation;
onSubmit: (data: ICstRelocateData) => void;
}
function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgRelocateConstituentsProps) {
const library = useLibrary();
const schemas = useMemo(() => {
const node = oss.graph.at(target.id)!;
const ids: LibraryItemID[] = [
...node.inputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null),
...node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null)
];
return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined);
}, [oss, library.items]);
const [destination, setDestination] = useState<ILibraryItem | undefined>(undefined);
const [selected, setSelected] = useState<ConstituentaID[]>([]);
const source = useRSFormDetails({ target: String(target.result!) });
const filtered = useMemo(() => {
if (!source.schema || !destination) {
return [];
}
const destinationOperation = oss.items.find(item => item.result === destination.id);
return getRelocateCandidates(target.id, destinationOperation!.id, source.schema, oss);
}, [destination, source.schema?.items]);
const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]);
useLayoutEffect(() => {
setSelected([]);
}, [destination]);
const handleSelectDestination = useCallback((newValue: ILibraryItem | undefined) => {
setDestination(newValue);
}, []);
const handleSubmit = useCallback(() => {
const data: ICstRelocateData = {
destination: target.result ?? 0,
items: []
};
onSubmit(data);
}, [target, onSubmit]);
return (
<Modal
header='Перемещение конституент'
submitText='Переместить'
hideWindow={hideWindow}
canSubmit={isValid}
onSubmit={handleSubmit}
className={clsx('w-[40rem] h-[33rem]', 'py-3 px-6')}
>
<DataLoader id='dlg-relocate-constituents' className='cc-column' isLoading={source.loading} error={source.error}>
<SelectLibraryItem
placeholder='Выберите целевую схему'
items={schemas}
value={destination}
onSelectValue={handleSelectDestination}
/>
{source.schema ? (
<PickMultiConstituenta
schema={source.schema}
data={filtered}
rows={12}
prefixID={prefixes.dlg_cst_constituents_list}
selected={selected}
setSelected={setSelected}
/>
) : null}
</DataLoader>
</Modal>
);
}
export default DlgRelocateConstituents;

View File

@ -86,30 +86,25 @@ export class Graph {
return !!this.nodes.get(target); return !!this.nodes.get(target);
} }
removeNode(target: number): GraphNode | null { removeNode(target: number): void {
const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) {
return null;
}
this.nodes.forEach(node => { this.nodes.forEach(node => {
node.removeInput(nodeToRemove.id); node.removeInput(target);
node.removeOutput(nodeToRemove.id); node.removeOutput(target);
}); });
this.nodes.delete(target); this.nodes.delete(target);
return nodeToRemove;
} }
foldNode(target: number): GraphNode | null { foldNode(target: number): void {
const nodeToRemove = this.nodes.get(target); const nodeToRemove = this.nodes.get(target);
if (!nodeToRemove) { if (!nodeToRemove) {
return null; return;
} }
nodeToRemove.inputs.forEach(input => { nodeToRemove.inputs.forEach(input => {
nodeToRemove.outputs.forEach(output => { nodeToRemove.outputs.forEach(output => {
this.addEdge(input, output); this.addEdge(input, output);
}); });
}); });
return this.removeNode(target); this.removeNode(target);
} }
removeIsolated(): GraphNode[] { removeIsolated(): GraphNode[] {
@ -124,6 +119,9 @@ export class Graph {
} }
addEdge(source: number, destination: number): void { addEdge(source: number, destination: number): void {
if (this.hasEdge(source, destination)) {
return;
}
const sourceNode = this.addNode(source); const sourceNode = this.addNode(source);
const destinationNode = this.addNode(destination); const destinationNode = this.addNode(destination);
sourceNode.addOutput(destinationNode.id); sourceNode.addOutput(destinationNode.id);

View File

@ -125,6 +125,14 @@ export interface ICstSubstituteData {
substitutions: ICstSubstitute[]; substitutions: ICstSubstitute[];
} }
/**
* Represents data, used relocating {@link IConstituenta}s between {@link ILibraryItem}s.
*/
export interface ICstRelocateData {
destination: LibraryItemID;
items: ConstituentaID[];
}
/** /**
* Represents substitution for multi synthesis table. * Represents substitution for multi synthesis table.
*/ */

View File

@ -8,7 +8,7 @@ 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, OperationID, SubstitutionErrorType } from './oss';
import { ConstituentaID, CstClass, 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';
@ -424,3 +424,45 @@ export class SubstitutionValidator {
return false; return false;
} }
} }
/**
* Filter relocate candidates from gives schema.
*/
export function getRelocateCandidates(
source: OperationID,
destination: OperationID,
schema: IRSForm,
oss: IOperationSchema
): IConstituenta[] {
const destinationSchema = oss.operationByID.get(destination)?.result;
if (!destinationSchema) {
return [];
}
const node = oss.graph.at(source);
if (!node) {
return [];
}
const addedCst = schema.items.filter(item => !item.is_inherited);
if (node.outputs.includes(destination)) {
return addedCst;
}
const unreachableBases: ConstituentaID[] = [];
for (const cst of schema.items.filter(item => item.is_inherited)) {
if (cst.parent_schema == destinationSchema) {
continue;
}
const parent = schema.inheritance.find(item => item.child === cst.id && item.child_source === cst.schema)?.parent;
if (parent) {
const original = oss.substitutions.find(sub => sub.substitution === parent)?.original;
if (original) {
continue;
// TODO: test if original schema is destination schema
}
}
unreachableBases.push(cst.id);
}
const unreachable = schema.graph.expandAllOutputs(unreachableBases);
return addedCst.filter(cst => !unreachable.includes(cst.id));
}

View File

@ -2,7 +2,15 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { IconConnect, IconDestroy, IconEdit2, IconExecute, IconNewRSForm, IconRSForm } from '@/components/Icons'; import {
IconChild,
IconConnect,
IconDestroy,
IconEdit2,
IconExecute,
IconNewRSForm,
IconRSForm
} from '@/components/Icons';
import Dropdown from '@/components/ui/Dropdown'; import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton'; import DropdownButton from '@/components/ui/DropdownButton';
import useClickedOutside from '@/hooks/useClickedOutside'; import useClickedOutside from '@/hooks/useClickedOutside';
@ -25,6 +33,7 @@ interface NodeContextMenuProps extends ContextMenuData {
onEditSchema: (target: OperationID) => void; onEditSchema: (target: OperationID) => void;
onEditOperation: (target: OperationID) => void; onEditOperation: (target: OperationID) => void;
onExecuteOperation: (target: OperationID) => void; onExecuteOperation: (target: OperationID) => void;
onRelocateConstituents: (target: OperationID) => void;
} }
function NodeContextMenu({ function NodeContextMenu({
@ -36,7 +45,8 @@ function NodeContextMenu({
onCreateInput, onCreateInput,
onEditSchema, onEditSchema,
onEditOperation, onEditOperation,
onExecuteOperation onExecuteOperation,
onRelocateConstituents
}: NodeContextMenuProps) { }: NodeContextMenuProps) {
const controller = useOssEdit(); const controller = useOssEdit();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@ -100,6 +110,11 @@ function NodeContextMenu({
onExecuteOperation(operation.id); onExecuteOperation(operation.id);
}; };
const handleRelocateConstituents = () => {
handleHide();
onRelocateConstituents(operation.id);
};
return ( return (
<div ref={ref} className='absolute select-none' style={{ top: cursorY, left: cursorX }}> <div ref={ref} className='absolute select-none' style={{ top: cursorY, left: cursorX }}>
<Dropdown <Dropdown
@ -156,6 +171,16 @@ function NodeContextMenu({
/> />
) : null} ) : null}
{controller.isMutable && operation.result ? (
<DropdownButton
text='Конституенты'
titleHtml='Перемещение конституент</br>между схемами'
icon={<IconChild size='1rem' className='icon-green' />}
disabled={controller.isProcessing}
onClick={handleRelocateConstituents}
/>
) : null}
<DropdownButton <DropdownButton
text='Удалить операцию' text='Удалить операцию'
icon={<IconDestroy size='1rem' className='icon-red' />} icon={<IconDestroy size='1rem' className='icon-red' />}

View File

@ -197,6 +197,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
handleExecuteOperation(controller.selected[0]); handleExecuteOperation(controller.selected[0]);
}, [controller, handleExecuteOperation]); }, [controller, handleExecuteOperation]);
const handleRelocateConstituents = useCallback(
(target: OperationID) => {
controller.promptRelocateConstituents(target);
},
[controller]
);
const handleFitView = useCallback(() => { const handleFitView = useCallback(() => {
flow.fitView({ duration: PARAMETER.zoomDuration }); flow.fitView({ duration: PARAMETER.zoomDuration });
}, [flow]); }, [flow]);
@ -376,6 +383,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
onEditSchema={handleEditSchema} onEditSchema={handleEditSchema}
onEditOperation={handleEditOperation} onEditOperation={handleEditOperation}
onExecuteOperation={handleExecuteOperation} onExecuteOperation={handleExecuteOperation}
onRelocateConstituents={handleRelocateConstituents}
{...menuProps} {...menuProps}
/> />
) : null} ) : null}

View File

@ -17,10 +17,12 @@ import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
import DlgDeleteOperation from '@/dialogs/DlgDeleteOperation'; import DlgDeleteOperation from '@/dialogs/DlgDeleteOperation';
import DlgEditEditors from '@/dialogs/DlgEditEditors'; import DlgEditEditors from '@/dialogs/DlgEditEditors';
import DlgEditOperation from '@/dialogs/DlgEditOperation'; import DlgEditOperation from '@/dialogs/DlgEditOperation';
import DlgRelocateConstituents from '@/dialogs/DlgRelocateConstituents';
import { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library'; import { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library';
import { Position2D } from '@/models/miscellaneous'; import { Position2D } from '@/models/miscellaneous';
import { calculateInsertPosition } from '@/models/miscellaneousAPI'; import { calculateInsertPosition } from '@/models/miscellaneousAPI';
import { import {
ICstRelocateData,
IOperationCreateData, IOperationCreateData,
IOperationDeleteData, IOperationDeleteData,
IOperationPosition, IOperationPosition,
@ -74,6 +76,7 @@ export interface IOssEditContext extends ILibraryItemEditor {
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
executeOperation: (target: OperationID, positions: IOperationPosition[]) => void; executeOperation: (target: OperationID, positions: IOperationPosition[]) => void;
promptRelocateConstituents: (target: OperationID) => void;
} }
const OssEditContext = createContext<IOssEditContext | null>(null); const OssEditContext = createContext<IOssEditContext | null>(null);
@ -110,6 +113,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
const [showEditInput, setShowEditInput] = useState(false); const [showEditInput, setShowEditInput] = useState(false);
const [showEditOperation, setShowEditOperation] = useState(false); const [showEditOperation, setShowEditOperation] = useState(false);
const [showDeleteOperation, setShowDeleteOperation] = useState(false); const [showDeleteOperation, setShowDeleteOperation] = useState(false);
const [showRelocateConstituents, setShowRelocateConstituents] = 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 });
@ -359,6 +363,23 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
[model] [model]
); );
const promptRelocateConstituents = useCallback(
(target: OperationID) => {
setTargetOperationID(target);
setShowRelocateConstituents(true);
},
[model]
);
const handleRelocateConstituents = useCallback(
(data: ICstRelocateData) => {
// TODO: implement backed call
console.log(data);
toast.success('В разработке');
},
[model]
);
return ( return (
<OssEditContext.Provider <OssEditContext.Provider
value={{ value={{
@ -388,7 +409,8 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
createInput, createInput,
promptEditInput, promptEditInput,
promptEditOperation, promptEditOperation,
executeOperation executeOperation,
promptRelocateConstituents
}} }}
> >
{model.schema ? ( {model.schema ? (
@ -438,6 +460,15 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
onSubmit={deleteOperation} onSubmit={deleteOperation}
/> />
) : null} ) : null}
{showRelocateConstituents ? (
<DlgRelocateConstituents
hideWindow={() => setShowRelocateConstituents(false)}
target={targetOperation!}
oss={model.schema}
onSubmit={handleRelocateConstituents}
/>
) : null}
)
</AnimatePresence> </AnimatePresence>
) : null} ) : null}

View File

@ -69,7 +69,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
); );
return ( return (
<div className='flex border-b clr-input'> <div className='flex border-b clr-input rounded-t-md'>
<SearchBar <SearchBar
id='constituents_search' id='constituents_search'
noBorder noBorder

View File

@ -182,5 +182,6 @@ export const prefixes = {
user_editors: 'user_editors_', user_editors: 'user_editors_',
wordform_list: 'wordform_list_', wordform_list: 'wordform_list_',
rsedit_btn: 'rsedit_btn_', rsedit_btn: 'rsedit_btn_',
dlg_cst_substitutes_list: 'dlg_cst_substitutes_list_' dlg_cst_substitutes_list: 'dlg_cst_substitutes_list_',
dlg_cst_constituents_list: 'dlg_cst_constituents_list_'
}; };