F: Constituenta relocation pt1
This commit is contained in:
parent
b1bfbe26cb
commit
1da81dbfc1
|
@ -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}
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
96
rsconcept/frontend/src/dialogs/DlgRelocateConstituents.tsx
Normal file
96
rsconcept/frontend/src/dialogs/DlgRelocateConstituents.tsx
Normal 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;
|
|
@ -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);
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -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' />}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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_'
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user