mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Implement graph operations and improve UI
This commit is contained in:
parent
79be1167be
commit
b33dceebf8
|
@ -59,7 +59,7 @@ function ConstituentaMultiPicker({ id, schema, prefixID, rows, selected, setSele
|
|||
if (!schema || selected.length === 0) {
|
||||
return;
|
||||
}
|
||||
const addition = schema.graph.expandInputs(selected).filter(id => !selected.includes(id));
|
||||
const addition = schema.graph.expandAllInputs(selected).filter(id => !selected.includes(id));
|
||||
if (addition.length > 0) {
|
||||
setSelected([...selected, ...addition]);
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ function ConstituentaMultiPicker({ id, schema, prefixID, rows, selected, setSele
|
|||
if (!schema || selected.length === 0) {
|
||||
return;
|
||||
}
|
||||
const addition = schema.graph.expandOutputs(selected).filter(id => !selected.includes(id));
|
||||
const addition = schema.graph.expandAllOutputs(selected).filter(id => !selected.includes(id));
|
||||
if (addition.length > 0) {
|
||||
setSelected([...selected, ...addition]);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ function MiniButton({
|
|||
type='button'
|
||||
tabIndex={tabIndex ?? -1}
|
||||
className={clsx(
|
||||
'rounded-full',
|
||||
'rounded-lg',
|
||||
'clr-btn-clear',
|
||||
'cursor-pointer disabled:cursor-not-allowed',
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@ function TabLabel({ label, title, titleHtml, hideTitle, className, ...otherProps
|
|||
return (
|
||||
<TabImpl
|
||||
className={clsx(
|
||||
'min-w-[6rem] h-full',
|
||||
'min-w-[5.5rem] h-full',
|
||||
'px-2 py-1 flex justify-center',
|
||||
'clr-tab',
|
||||
'text-sm whitespace-nowrap font-controls',
|
||||
|
|
|
@ -18,7 +18,7 @@ interface DlgDeleteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
|
||||
function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstProps) {
|
||||
const [expandOut, setExpandOut] = useState(false);
|
||||
const expansion: number[] = useMemo(() => schema.graph.expandOutputs(selected), [selected, schema.graph]);
|
||||
const expansion: number[] = useMemo(() => schema.graph.expandAllOutputs(selected), [selected, schema.graph]);
|
||||
|
||||
function handleSubmit() {
|
||||
hideWindow();
|
||||
|
|
|
@ -88,6 +88,7 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
|
|||
value={version}
|
||||
onChange={event => setVersion(event.target.value)}
|
||||
/>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
title='Сохранить изменения'
|
||||
disabled={!isModified || !isValid || processing}
|
||||
|
@ -101,6 +102,7 @@ function DlgEditVersions({ hideWindow, versions, onDelete, onUpdate }: DlgEditVe
|
|||
icon={<BiReset size='1.25rem' className='icon-primary' />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<TextArea
|
||||
id='dlg_description'
|
||||
spellCheck
|
||||
|
|
|
@ -95,11 +95,22 @@ describe('Testing Graph sort', () => {
|
|||
|
||||
describe('Testing Graph queries', () => {
|
||||
test('expand outputs', () => {
|
||||
const graph = new Graph([[1, 2], [2, 3], [2, 5], [5, 6], [6, 1], [7]]);
|
||||
const graph = new Graph([
|
||||
[1, 2], //
|
||||
[2, 3],
|
||||
[2, 5],
|
||||
[5, 6],
|
||||
[6, 1],
|
||||
[7]
|
||||
]);
|
||||
expect(graph.expandOutputs([])).toStrictEqual([]);
|
||||
expect(graph.expandAllOutputs([])).toStrictEqual([]);
|
||||
expect(graph.expandOutputs([3])).toStrictEqual([]);
|
||||
expect(graph.expandAllOutputs([3])).toStrictEqual([]);
|
||||
expect(graph.expandOutputs([7])).toStrictEqual([]);
|
||||
expect(graph.expandOutputs([2, 5])).toStrictEqual([3, 6, 1]);
|
||||
expect(graph.expandAllOutputs([7])).toStrictEqual([]);
|
||||
expect(graph.expandOutputs([2, 5])).toStrictEqual([3, 6]);
|
||||
expect(graph.expandAllOutputs([2, 5])).toStrictEqual([3, 6, 1]);
|
||||
});
|
||||
|
||||
test('expand into unique array', () => {
|
||||
|
@ -109,13 +120,43 @@ describe('Testing Graph queries', () => {
|
|||
[2, 5],
|
||||
[3, 5]
|
||||
]);
|
||||
expect(graph.expandOutputs([1])).toStrictEqual([2, 3, 5]);
|
||||
expect(graph.expandAllOutputs([1])).toStrictEqual([2, 3, 5]);
|
||||
});
|
||||
|
||||
test('expand inputs', () => {
|
||||
const graph = new Graph([[1, 2], [2, 3], [2, 5], [5, 6], [6, 1], [7]]);
|
||||
const graph = new Graph([
|
||||
[1, 2], //
|
||||
[2, 3],
|
||||
[2, 5],
|
||||
[5, 6],
|
||||
[6, 1],
|
||||
[7]
|
||||
]);
|
||||
expect(graph.expandInputs([])).toStrictEqual([]);
|
||||
expect(graph.expandAllInputs([])).toStrictEqual([]);
|
||||
expect(graph.expandInputs([7])).toStrictEqual([]);
|
||||
expect(graph.expandInputs([6])).toStrictEqual([5, 2, 1]);
|
||||
expect(graph.expandAllInputs([7])).toStrictEqual([]);
|
||||
expect(graph.expandInputs([6])).toStrictEqual([5]);
|
||||
expect(graph.expandAllInputs([6])).toStrictEqual([5, 2, 1]);
|
||||
});
|
||||
|
||||
test('maximize part', () => {
|
||||
const graph = new Graph([
|
||||
[1, 7], //
|
||||
[1, 3],
|
||||
[2, 3],
|
||||
[2, 4],
|
||||
[3, 5],
|
||||
[3, 6],
|
||||
[3, 4],
|
||||
[7, 5],
|
||||
[8]
|
||||
]);
|
||||
expect(graph.maximizePart([])).toStrictEqual([]);
|
||||
expect(graph.maximizePart([8])).toStrictEqual([8]);
|
||||
expect(graph.maximizePart([5])).toStrictEqual([5]);
|
||||
expect(graph.maximizePart([3])).toStrictEqual([3, 6]);
|
||||
expect(graph.maximizePart([3, 2])).toStrictEqual([3, 2, 6, 4]);
|
||||
expect(graph.maximizePart([3, 1])).toStrictEqual([3, 1, 7, 5, 6]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -144,18 +144,42 @@ export class Graph {
|
|||
|
||||
expandOutputs(origin: number[]): number[] {
|
||||
const result: number[] = [];
|
||||
const marked = new Map<number, boolean>();
|
||||
origin.forEach(id => marked.set(id, true));
|
||||
origin.forEach(id => {
|
||||
const node = this.nodes.get(id);
|
||||
if (node) {
|
||||
node.outputs.forEach(child => {
|
||||
if (!marked.get(child) && !result.find(id => id === child)) {
|
||||
if (!origin.includes(child) && !result.includes(child)) {
|
||||
result.push(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
expandInputs(origin: number[]): number[] {
|
||||
const result: number[] = [];
|
||||
origin.forEach(id => {
|
||||
const node = this.nodes.get(id);
|
||||
if (node) {
|
||||
node.inputs.forEach(child => {
|
||||
if (!origin.includes(child) && !result.includes(child)) {
|
||||
result.push(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
expandAllOutputs(origin: number[]): number[] {
|
||||
const result: number[] = this.expandOutputs(origin);
|
||||
if (result.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const marked = new Map<number, boolean>();
|
||||
origin.forEach(id => marked.set(id, true));
|
||||
let position = 0;
|
||||
while (position < result.length) {
|
||||
const node = this.nodes.get(result[position]);
|
||||
|
@ -172,20 +196,14 @@ export class Graph {
|
|||
return result;
|
||||
}
|
||||
|
||||
expandInputs(origin: number[]): number[] {
|
||||
const result: number[] = [];
|
||||
expandAllInputs(origin: number[]): number[] {
|
||||
const result: number[] = this.expandInputs(origin);
|
||||
if (result.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const marked = new Map<number, boolean>();
|
||||
origin.forEach(id => marked.set(id, true));
|
||||
origin.forEach(id => {
|
||||
const node = this.nodes.get(id);
|
||||
if (node) {
|
||||
node.inputs.forEach(child => {
|
||||
if (!marked.get(child) && !result.find(id => id === child)) {
|
||||
result.push(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let position = 0;
|
||||
while (position < result.length) {
|
||||
const node = this.nodes.get(result[position]);
|
||||
|
@ -202,6 +220,20 @@ export class Graph {
|
|||
return result;
|
||||
}
|
||||
|
||||
maximizePart(origin: number[]): number[] {
|
||||
const outputs: number[] = this.expandAllOutputs(origin);
|
||||
const result = [...origin];
|
||||
this.topologicalOrder()
|
||||
.filter(id => outputs.includes(id))
|
||||
.forEach(id => {
|
||||
const node = this.nodes.get(id);
|
||||
if (node?.inputs.every(parent => result.includes(parent))) {
|
||||
result.push(id);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
topologicalOrder(): number[] {
|
||||
const result: number[] = [];
|
||||
const marked = new Map<number, boolean>();
|
||||
|
|
|
@ -27,10 +27,10 @@ export function applyGraphFilter(target: IRSForm, start: number, mode: Dependenc
|
|||
return target.graph.nodes.get(start)?.inputs;
|
||||
}
|
||||
case DependencyMode.EXPAND_OUTPUTS: {
|
||||
return target.graph.expandOutputs([start]);
|
||||
return target.graph.expandAllOutputs([start]);
|
||||
}
|
||||
case DependencyMode.EXPAND_INPUTS: {
|
||||
return target.graph.expandInputs([start]);
|
||||
return target.graph.expandAllInputs([start]);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
|
|
@ -31,7 +31,7 @@ function ConstituentaToolbar({
|
|||
onCreate
|
||||
}: ConstituentaToolbarProps) {
|
||||
return (
|
||||
<Overlay position='top-1 right-4 sm:right-1/2 sm:translate-x-1/2' className='flex'>
|
||||
<Overlay position='top-1 right-4 sm:right-1/2 sm:translate-x-1/2' className='cc-icons'>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||
icon={<FiSave size='1.25rem' className='icon-primary' />}
|
||||
|
|
|
@ -18,7 +18,7 @@ interface ControlsOverlayProps {
|
|||
|
||||
function ControlsOverlay({ constituenta, disabled, modified, processing, onRename, onEditTerm }: ControlsOverlayProps) {
|
||||
return (
|
||||
<Overlay position='top-1 left-[4.1rem]' className='flex select-none'>
|
||||
<Overlay position='top-1 left-[4.3rem]' className='flex select-none'>
|
||||
{!disabled || processing ? (
|
||||
<MiniButton
|
||||
title={
|
||||
|
|
|
@ -161,7 +161,7 @@ function EditorRSExpression({
|
|||
) : null}
|
||||
</AnimatePresence>
|
||||
|
||||
<Overlay position='top-[-0.5rem] right-0 flex'>
|
||||
<Overlay position='top-[-0.5rem] right-0 cc-icons'>
|
||||
<MiniButton
|
||||
title='Изменить шрифт'
|
||||
onClick={toggleFont}
|
||||
|
@ -169,20 +169,17 @@ function EditorRSExpression({
|
|||
/>
|
||||
{!disabled || model.processing ? (
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Отображение специальной клавиатуры'
|
||||
onClick={() => setShowControls(prev => !prev)}
|
||||
icon={<FaRegKeyboard size='1.25rem' className={showControls ? 'icon-primary' : ''} />}
|
||||
/>
|
||||
) : null}
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Отображение списка конституент'
|
||||
onClick={onToggleList}
|
||||
icon={<BiListUl size='1.25rem' className={showList ? 'icon-primary' : ''} />}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Дерево разбора выражения'
|
||||
onClick={handleShowAST}
|
||||
icon={<RiNodeTree size='1.25rem' className='icon-primary' />}
|
||||
|
|
|
@ -117,18 +117,16 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
|
|||
onChange={event => setAlias(event.target.value)}
|
||||
/>
|
||||
<div className='flex flex-col'>
|
||||
<Overlay position='top-[-0.25rem] right-[-0.25rem] flex'>
|
||||
<Overlay position='top-[-0.25rem] right-[-0.25rem] cc-icons'>
|
||||
{controller.isMutable ? (
|
||||
<>
|
||||
<MiniButton
|
||||
noHover
|
||||
title={controller.isContentEditable ? 'Создать версию' : 'Переключитесь на актуальную версию'}
|
||||
disabled={!controller.isContentEditable}
|
||||
onClick={controller.createVersion}
|
||||
icon={<LuGitBranchPlus size='1.25rem' className='icon-green' />}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title={schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
|
||||
disabled={!schema || schema?.versions.length === 0}
|
||||
onClick={controller.editVersions}
|
||||
|
|
|
@ -26,7 +26,7 @@ function RSFormToolbar({ modified, anonymous, subscribed, claimable, onSubmit, o
|
|||
const controller = useRSEdit();
|
||||
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
|
||||
return (
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
|
||||
{controller.isContentEditable ? (
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
|
||||
|
|
|
@ -19,7 +19,7 @@ function RSListToolbar() {
|
|||
const insertMenu = useDropdown();
|
||||
|
||||
return (
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex items-start'>
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='items-start cc-icons'>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
|
||||
icon={<BiUpvote size='1.25rem' className='icon-primary' />}
|
||||
|
|
|
@ -18,7 +18,6 @@ import { storage, TIMEOUT_GRAPH_REFRESH } from '@/utils/constants';
|
|||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
import GraphSelectors from './GraphSelectors';
|
||||
import GraphSidebar from './GraphSidebar';
|
||||
import GraphToolbar from './GraphToolbar';
|
||||
import TermGraph from './TermGraph';
|
||||
import useGraphFilter from './useGraphFilter';
|
||||
|
@ -158,6 +157,10 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
event.preventDefault();
|
||||
handleDeleteCst();
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
controller.deselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
const graph = useMemo(
|
||||
|
@ -254,7 +257,6 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
onEdit={onOpenEdit}
|
||||
/>
|
||||
</div>
|
||||
<GraphSidebar />
|
||||
</Overlay>
|
||||
|
||||
{graph}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import { BiGitBranch, BiGitMerge, BiReset } from 'react-icons/bi';
|
||||
import { LuExpand, LuMaximize, LuMinimize } from 'react-icons/lu';
|
||||
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
|
||||
import { useRSEdit } from '../RSEditContext';
|
||||
|
||||
function GraphSidebar() {
|
||||
const controller = useRSEdit();
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-1 clr-app'>
|
||||
<MiniButton
|
||||
titleHtml='<b>Сбросить выделение</b>'
|
||||
icon={<BiReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={controller.deselectAll}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='<b>Выделение базиса</b> - замыкание выделения влияющими конституентами'
|
||||
icon={<LuMinimize size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='<b>Максимизация части</b> - замыкание выделения конституентами, зависимыми только от выделенных'
|
||||
icon={<LuMaximize size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Выделить все зависимые'
|
||||
icon={<LuExpand size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Выделить поставщиков'
|
||||
icon={<BiGitBranch size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Выделить потребителей'
|
||||
icon={<BiGitMerge size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GraphSidebar;
|
|
@ -1,7 +1,17 @@
|
|||
'use client';
|
||||
|
||||
import { BiFilterAlt, BiFont, BiFontFamily, BiPlanet, BiPlusCircle, BiTrash } from 'react-icons/bi';
|
||||
import { LuImage } from 'react-icons/lu';
|
||||
import {
|
||||
BiFilterAlt,
|
||||
BiFont,
|
||||
BiFontFamily,
|
||||
BiGitBranch,
|
||||
BiGitMerge,
|
||||
BiPlanet,
|
||||
BiPlusCircle,
|
||||
BiReset,
|
||||
BiTrash
|
||||
} from 'react-icons/bi';
|
||||
import { LuExpand, LuImage, LuMaximize, LuMinimize } from 'react-icons/lu';
|
||||
|
||||
import BadgeHelp from '@/components/man/BadgeHelp';
|
||||
import MiniButton from '@/components/ui/MiniButton';
|
||||
|
@ -39,7 +49,11 @@ function GraphToolbar({
|
|||
const controller = useRSEdit();
|
||||
|
||||
return (
|
||||
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2 clr-app' className='flex'>
|
||||
<Overlay
|
||||
position='top-0 pt-1 right-1/2 translate-x-1/2'
|
||||
className='flex flex-col items-center bg-opacity-10 clr-app'
|
||||
>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
title='Настройки фильтрации узлов и связей'
|
||||
icon={<BiFilterAlt size='1.25rem' className='icon-primary' />}
|
||||
|
@ -84,6 +98,44 @@ function GraphToolbar({
|
|||
/>
|
||||
) : null}
|
||||
<BadgeHelp topic={HelpTopic.GRAPH_TERM} className='max-w-[calc(100vw-4rem)]' offset={4} />
|
||||
</div>
|
||||
<div className='cc-icons'>
|
||||
<MiniButton
|
||||
titleHtml='<b>[ESC]</b><br/>Сбросить выделение'
|
||||
icon={<BiReset size='1.25rem' className='icon-primary' />}
|
||||
onClick={controller.deselectAll}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='<b>Замыкание</b> - дополнение выделения влияющими конституентами'
|
||||
icon={<LuMinimize size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
onClick={controller.selectAllInputs}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='<b>Максимизация</b> - дополнение выделения конституентами, зависимыми только от выделенных'
|
||||
icon={<LuMaximize size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
onClick={controller.selectMax}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить все зависимые'
|
||||
icon={<LuExpand size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
onClick={controller.selectAllOutputs}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить поставщиков'
|
||||
icon={<BiGitBranch size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
onClick={controller.selectInputs}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml='Выделить потребителей'
|
||||
icon={<BiGitMerge size='1.25rem' className='icon-primary' />}
|
||||
disabled={controller.nothingSelected}
|
||||
onClick={controller.selectOutputs}
|
||||
/>
|
||||
</div>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -58,6 +58,11 @@ interface IRSEditContext {
|
|||
|
||||
setSelection: (selected: ConstituentaID[]) => void;
|
||||
select: (target: ConstituentaID) => void;
|
||||
selectAllInputs: () => void;
|
||||
selectAllOutputs: () => void;
|
||||
selectMax: () => void;
|
||||
selectInputs: () => void;
|
||||
selectOutputs: () => void;
|
||||
deselect: (target: ConstituentaID) => void;
|
||||
toggleSelect: (target: ConstituentaID) => void;
|
||||
deselectAll: () => void;
|
||||
|
@ -484,6 +489,11 @@ export const RSEditState = ({
|
|||
setSelection: (selected: ConstituentaID[]) => setSelected(selected),
|
||||
select: (target: ConstituentaID) => setSelected(prev => [...prev, target]),
|
||||
deselect: (target: ConstituentaID) => setSelected(prev => prev.filter(id => id !== target)),
|
||||
selectAllInputs: () => setSelected(prev => [...prev, ...(model.schema?.graph.expandAllInputs(prev) ?? [])]),
|
||||
selectAllOutputs: () => setSelected(prev => [...prev, ...(model.schema?.graph.expandAllOutputs(prev) ?? [])]),
|
||||
selectOutputs: () => setSelected(prev => [...prev, ...(model.schema?.graph.expandOutputs(prev) ?? [])]),
|
||||
selectInputs: () => setSelected(prev => [...prev, ...(model.schema?.graph.expandInputs(prev) ?? [])]),
|
||||
selectMax: () => setSelected(prev => model.schema?.graph.maximizePart(prev) ?? []),
|
||||
toggleSelect: (target: ConstituentaID) =>
|
||||
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
|
||||
deselectAll: () => setSelected([]),
|
||||
|
|
|
@ -204,7 +204,7 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
|||
tabIndex={-1}
|
||||
title={'Редактирование'}
|
||||
hideTitle={editMenu.isOpen}
|
||||
className='h-full'
|
||||
className='h-full px-2'
|
||||
icon={<FiEdit size='1.25rem' className={controller.isContentEditable ? 'icon-green' : 'icon-red'} />}
|
||||
onClick={editMenu.toggle}
|
||||
/>
|
||||
|
|
|
@ -179,7 +179,7 @@ export const graphLightT = {
|
|||
activeFill: '#1DE9AC',
|
||||
opacity: 1,
|
||||
selectedOpacity: 1,
|
||||
inactiveOpacity: 0.5,
|
||||
inactiveOpacity: 1,
|
||||
label: {
|
||||
color: '#2A6475',
|
||||
stroke: '#fff',
|
||||
|
@ -199,7 +199,7 @@ export const graphLightT = {
|
|||
activeFill: '#1DE9AC',
|
||||
opacity: 1,
|
||||
selectedOpacity: 1,
|
||||
inactiveOpacity: 0.1,
|
||||
inactiveOpacity: 1,
|
||||
label: {
|
||||
stroke: '#fff',
|
||||
color: '#2A6475',
|
||||
|
@ -209,13 +209,6 @@ export const graphLightT = {
|
|||
arrow: {
|
||||
fill: '#D8E6EA',
|
||||
activeFill: '#1DE9AC'
|
||||
},
|
||||
cluster: {
|
||||
stroke: '#D8E6EA',
|
||||
label: {
|
||||
stroke: '#fff',
|
||||
color: '#2A6475'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -231,7 +224,7 @@ export const graphDarkT = {
|
|||
activeFill: '#1DE9AC',
|
||||
opacity: 1,
|
||||
selectedOpacity: 1,
|
||||
inactiveOpacity: 0.5,
|
||||
inactiveOpacity: 1,
|
||||
label: {
|
||||
stroke: '#1E2026',
|
||||
color: '#ACBAC7',
|
||||
|
@ -251,7 +244,7 @@ export const graphDarkT = {
|
|||
activeFill: '#1DE9AC',
|
||||
opacity: 1,
|
||||
selectedOpacity: 1,
|
||||
inactiveOpacity: 0.1,
|
||||
inactiveOpacity: 1,
|
||||
label: {
|
||||
stroke: '#1E2026',
|
||||
color: '#ACBAC7',
|
||||
|
@ -261,13 +254,6 @@ export const graphDarkT = {
|
|||
arrow: {
|
||||
fill: '#474B56',
|
||||
activeFill: '#1DE9AC'
|
||||
},
|
||||
cluster: {
|
||||
stroke: '#474B56',
|
||||
label: {
|
||||
stroke: '#1E2026',
|
||||
color: '#ACBAC7'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -206,4 +206,8 @@
|
|||
.cc-column {
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
|
||||
.cc-icons {
|
||||
@apply flex gap-1;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user