mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
F: Constituenta relocation: final part
This commit is contained in:
parent
0d14151469
commit
9761cc2c9d
|
@ -16,4 +16,4 @@ djangorestframework-stubs==3.15.1
|
|||
django-extensions==3.2.3
|
||||
mypy==1.11.2
|
||||
pylint==3.3.1
|
||||
coverage==7.6.3
|
||||
coverage==7.6.4
|
|
@ -1,14 +1,14 @@
|
|||
tzdata==2024.1
|
||||
Django==5.1.1
|
||||
tzdata==2024.2
|
||||
Django==5.1.2
|
||||
djangorestframework==3.15.2
|
||||
django-cors-headers==4.4.0
|
||||
django-cors-headers==4.5.0
|
||||
django-filter==24.3
|
||||
drf-spectacular==0.27.2
|
||||
drf-spectacular-sidecar==2024.7.1
|
||||
coreapi==2.3.3
|
||||
django-rest-passwordreset==1.4.1
|
||||
django-rest-passwordreset==1.4.2
|
||||
cctext==0.1.4
|
||||
pyconcept==0.1.10
|
||||
pyconcept==0.1.11
|
||||
|
||||
psycopg2-binary==2.9.9
|
||||
psycopg2-binary==2.9.10
|
||||
gunicorn==23.0.0
|
|
@ -20,6 +20,8 @@ import {
|
|||
IconGraphInputs,
|
||||
IconGraphOutputs,
|
||||
IconHide,
|
||||
IconMoveDown,
|
||||
IconMoveUp,
|
||||
IconOSS,
|
||||
IconPrivate,
|
||||
IconProps,
|
||||
|
@ -159,3 +161,11 @@ export function CstTypeIcon({ value, size = '1.25rem', className }: DomIconProps
|
|||
return <IconCstTheorem size={size} className={className ?? 'clr-text-red'} />;
|
||||
}
|
||||
}
|
||||
|
||||
export function RelocateUpIcon({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
|
||||
if (value) {
|
||||
return <IconMoveUp size={size} className={className ?? 'clr-text-primary'} />;
|
||||
} else {
|
||||
return <IconMoveDown size={size} className={className ?? 'clr-text-primary'} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ interface PickMultiConstituentaProps {
|
|||
|
||||
prefixID: string;
|
||||
rows?: number;
|
||||
noBorder?: boolean;
|
||||
|
||||
selected: ConstituentaID[];
|
||||
setSelected: React.Dispatch<React.SetStateAction<ConstituentaID[]>>;
|
||||
|
@ -36,6 +37,7 @@ function PickMultiConstituenta({
|
|||
data,
|
||||
prefixID,
|
||||
rows,
|
||||
noBorder,
|
||||
selected,
|
||||
setSelected
|
||||
}: PickMultiConstituentaProps) {
|
||||
|
@ -118,10 +120,10 @@ function PickMultiConstituenta({
|
|||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='flex justify-between items-center clr-input px-3 border-x border-t rounded-t-md'>
|
||||
<div className={noBorder ? '' : 'border'}>
|
||||
<div className={clsx('px-3 flex justify-between items-center', 'clr-input', 'border-b', 'rounded-t-md')}>
|
||||
<div className='w-[24ch] select-none whitespace-nowrap'>
|
||||
Выбраны {selected.length} из {data.length}
|
||||
{data.length > 0 ? `Выбраны ${selected.length} из ${data.length}` : 'Конституенты'}
|
||||
</div>
|
||||
<SearchBar
|
||||
id='dlg_constituents_search'
|
||||
|
@ -145,7 +147,7 @@ function PickMultiConstituenta({
|
|||
noFooter
|
||||
rows={rows}
|
||||
contentHeight='1.3rem'
|
||||
className={clsx('cc-scroll-y', 'border', 'text-sm', 'select-none')}
|
||||
className={clsx('cc-scroll-y', 'text-sm', 'select-none')}
|
||||
data={filtered}
|
||||
columns={columns}
|
||||
headPosition='0rem'
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { RelocateUpIcon } from '@/components/DomainIcons';
|
||||
import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
|
||||
import SelectLibraryItem from '@/components/select/SelectLibraryItem';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import DataLoader from '@/components/wrap/DataLoader';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
|
@ -17,41 +19,57 @@ import { prefixes } from '@/utils/constants';
|
|||
|
||||
interface DlgRelocateConstituentsProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
oss: IOperationSchema;
|
||||
target: IOperation;
|
||||
initialTarget?: IOperation;
|
||||
onSubmit: (data: ICstRelocateData) => void;
|
||||
}
|
||||
|
||||
function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgRelocateConstituentsProps) {
|
||||
function DlgRelocateConstituents({ oss, hideWindow, initialTarget, 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, target.id]);
|
||||
|
||||
const [directionUp, setDirectionUp] = useState(true);
|
||||
const [destination, setDestination] = useState<ILibraryItem | undefined>(undefined);
|
||||
const [selected, setSelected] = useState<ConstituentaID[]>([]);
|
||||
const [source, setSource] = useState<ILibraryItem | undefined>(
|
||||
library.items.find(item => item.id === initialTarget?.result)
|
||||
);
|
||||
|
||||
const source = useRSFormDetails({ target: String(target.result!) });
|
||||
const filtered = useMemo(() => {
|
||||
if (!source.schema || !destination) {
|
||||
const operation = useMemo(() => oss.items.find(item => item.result === source?.id), [oss, source]);
|
||||
const sourceSchemas = useMemo(() => library.items.filter(item => oss.schemas.includes(item.id)), [library, oss]);
|
||||
const destinationSchemas = useMemo(() => {
|
||||
if (!operation) {
|
||||
return [];
|
||||
}
|
||||
const node = oss.graph.at(operation.id)!;
|
||||
const ids: LibraryItemID[] = directionUp
|
||||
? 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, operation, directionUp]);
|
||||
|
||||
const sourceData = useRSFormDetails({ target: source ? String(source.id) : undefined });
|
||||
const filteredConstituents = useMemo(() => {
|
||||
if (!sourceData.schema || !destination || !operation) {
|
||||
return [];
|
||||
}
|
||||
const destinationOperation = oss.items.find(item => item.result === destination.id);
|
||||
return getRelocateCandidates(target.id, destinationOperation!.id, source.schema, oss);
|
||||
}, [destination, target.id, source.schema, oss]);
|
||||
return getRelocateCandidates(operation.id, destinationOperation!.id, sourceData.schema, oss);
|
||||
}, [destination, operation, sourceData.schema, oss]);
|
||||
|
||||
const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]);
|
||||
const toggleDirection = useCallback(() => {
|
||||
setDirectionUp(prev => !prev);
|
||||
setDestination(undefined);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handleSelectSource = useCallback((newValue: ILibraryItem | undefined) => {
|
||||
setSource(newValue);
|
||||
setDestination(undefined);
|
||||
setSelected([]);
|
||||
}, [destination]);
|
||||
}, []);
|
||||
|
||||
const handleSelectDestination = useCallback((newValue: ILibraryItem | undefined) => {
|
||||
setDestination(newValue);
|
||||
setSelected([]);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
@ -74,24 +92,44 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc
|
|||
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}
|
||||
<div className='flex flex-col border'>
|
||||
<div className='flex gap-1 items-center clr-input border-b rounded-t-md'>
|
||||
<SelectLibraryItem
|
||||
noBorder
|
||||
className='w-1/2'
|
||||
placeholder='Выберите исходную схему'
|
||||
items={sourceSchemas}
|
||||
value={source}
|
||||
onSelectValue={handleSelectSource}
|
||||
/>
|
||||
) : null}
|
||||
</DataLoader>
|
||||
<MiniButton
|
||||
title='Направление перемещения'
|
||||
icon={<RelocateUpIcon value={directionUp} />}
|
||||
onClick={toggleDirection}
|
||||
/>
|
||||
<SelectLibraryItem
|
||||
noBorder
|
||||
className='w-1/2'
|
||||
placeholder='Выберите целевую схему'
|
||||
items={destinationSchemas}
|
||||
value={destination}
|
||||
onSelectValue={handleSelectDestination}
|
||||
/>
|
||||
</div>
|
||||
<DataLoader id='dlg-relocate-constituents' isLoading={sourceData.loading} error={sourceData.error}>
|
||||
{sourceData.schema ? (
|
||||
<PickMultiConstituenta
|
||||
noBorder
|
||||
schema={sourceData.schema}
|
||||
data={filteredConstituents}
|
||||
rows={12}
|
||||
prefixID={prefixes.dlg_cst_constituents_list}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
/>
|
||||
) : null}
|
||||
</DataLoader>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { urls } from '@/app/urls';
|
|||
import {
|
||||
IconAdmin,
|
||||
IconAlert,
|
||||
IconChild,
|
||||
IconDestroy,
|
||||
IconEdit2,
|
||||
IconEditor,
|
||||
|
@ -67,6 +68,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
|
|||
router.push(urls.login);
|
||||
}
|
||||
|
||||
function handleRelocate() {
|
||||
editMenu.hide();
|
||||
controller.promptRelocateConstituents(undefined, []);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex'>
|
||||
<div ref={schemaMenu.ref}>
|
||||
|
@ -128,9 +134,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
|
|||
/>
|
||||
<Dropdown isOpen={editMenu.isOpen}>
|
||||
<DropdownButton
|
||||
text='см. Граф синтеза'
|
||||
titleHtml='Редактирование доступно <br/>через Граф синтеза'
|
||||
disabled
|
||||
text='Конституенты'
|
||||
titleHtml='Перемещение конституент</br>между схемами'
|
||||
icon={<IconChild size='1rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleRelocate}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
|
|
@ -76,7 +76,7 @@ export interface IOssEditContext extends ILibraryItemEditor {
|
|||
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
executeOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptRelocateConstituents: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptRelocateConstituents: (target: OperationID | undefined, positions: IOperationPosition[]) => void;
|
||||
}
|
||||
|
||||
const OssEditContext = createContext<IOssEditContext | null>(null);
|
||||
|
@ -360,7 +360,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
|
|||
[model]
|
||||
);
|
||||
|
||||
const promptRelocateConstituents = useCallback((target: OperationID, positions: IOperationPosition[]) => {
|
||||
const promptRelocateConstituents = useCallback((target: OperationID | undefined, positions: IOperationPosition[]) => {
|
||||
setPositions(positions);
|
||||
setTargetOperationID(target);
|
||||
setShowRelocateConstituents(true);
|
||||
|
@ -368,9 +368,18 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
|
|||
|
||||
const handleRelocateConstituents = useCallback(
|
||||
(data: ICstRelocateData) => {
|
||||
model.savePositions({ positions: positions }, () =>
|
||||
model.relocateConstituents(data, () => toast.success(information.changesSaved))
|
||||
);
|
||||
if (
|
||||
positions.every(item => {
|
||||
const operation = model.schema!.operationByID.get(item.id)!;
|
||||
return operation.position_x === item.position_x && operation.position_y === item.position_y;
|
||||
})
|
||||
) {
|
||||
model.relocateConstituents(data, () => toast.success(information.changesSaved));
|
||||
} else {
|
||||
model.savePositions({ positions: positions }, () =>
|
||||
model.relocateConstituents(data, () => toast.success(information.changesSaved))
|
||||
);
|
||||
}
|
||||
},
|
||||
[model, positions]
|
||||
);
|
||||
|
@ -458,7 +467,7 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
|
|||
{showRelocateConstituents ? (
|
||||
<DlgRelocateConstituents
|
||||
hideWindow={() => setShowRelocateConstituents(false)}
|
||||
target={targetOperation!}
|
||||
initialTarget={targetOperation}
|
||||
oss={model.schema}
|
||||
onSubmit={handleRelocateConstituents}
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue
Block a user