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