M: Minor UI improvements

This commit is contained in:
Ivan 2025-07-28 21:39:02 +03:00
parent 72f7be3247
commit de108074b1
9 changed files with 205 additions and 172 deletions

View File

@ -12,6 +12,7 @@ import {
IconGraphInputs, IconGraphInputs,
IconGraphMaximize, IconGraphMaximize,
IconGraphOutputs, IconGraphOutputs,
IconGraphSelection,
IconNewItem, IconNewItem,
IconOSS, IconOSS,
IconPredecessor, IconPredecessor,
@ -101,6 +102,9 @@ export function HelpRSGraphTerm() {
<div className='dense w-84'> <div className='dense w-84'>
<h2>Выделение</h2> <h2>Выделение</h2>
<ul> <ul>
<li>
<IconGraphSelection className='inline-icon' /> выделить связанные...
</li>
<li> <li>
<IconGraphCollapse className='inline-icon' /> все влияющие <IconGraphCollapse className='inline-icon' /> все влияющие
</li> </li>

View File

@ -73,7 +73,7 @@ export function DlgCreateSynthesis() {
return ( return (
<ModalForm <ModalForm
header='Создание операции' header='Создание операции синтеза'
submitText='Создать' submitText='Создать'
canSubmit={isValid} canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)} onSubmit={event => void methods.handleSubmit(onSubmit)(event)}

View File

@ -101,18 +101,15 @@ export function DlgEditOperation() {
<TabOperation /> <TabOperation />
</TabPanel> </TabPanel>
{target.operation_type === OperationType.SYNTHESIS ? ( <TabPanel>{target.operation_type === OperationType.SYNTHESIS ? <TabArguments /> : null}</TabPanel>
<TabPanel>
<TabArguments /> <TabPanel>
</TabPanel> {target.operation_type === OperationType.SYNTHESIS ? (
) : null}
{target.operation_type === OperationType.SYNTHESIS ? (
<TabPanel>
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<TabSubstitutions /> <TabSubstitutions />
</Suspense> </Suspense>
</TabPanel> ) : null}
) : null} </TabPanel>
</FormProvider> </FormProvider>
</Tabs> </Tabs>
</ModalForm> </ModalForm>

View File

@ -77,7 +77,7 @@ export function OssFlow() {
void updateLayout({ itemID: schema.id, data: getLayout() }); void updateLayout({ itemID: schema.id, data: getLayout() });
} }
function handleCreateOperation() { function handleCreateSynthesis() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateOperation({ showCreateOperation({
manager: new LayoutManager(schema, getLayout()), manager: new LayoutManager(schema, getLayout()),
@ -184,13 +184,23 @@ export function OssFlow() {
withPreventDefault(handleSavePositions)(event); withPreventDefault(handleSavePositions)(event);
return; return;
} }
if (event.altKey && event.code === 'Key1') { if (event.altKey) {
withPreventDefault(handleCreateBlock)(event); if (event.code === 'Digit1') {
return; withPreventDefault(handleCreateBlock)(event);
} return;
if (event.altKey && event.code === 'Key2') { }
withPreventDefault(handleCreateOperation)(event); if (event.code === 'Digit2') {
return; withPreventDefault(handleCreateSynthesis)(event);
return;
}
if (event.code === 'Digit3') {
withPreventDefault(handleImportSchema)(event);
return;
}
if (event.code === 'Digit4') {
withPreventDefault(handleCreateSynthesis)(event);
return;
}
} }
if (event.key === 'Delete') { if (event.key === 'Delete') {
withPreventDefault(handleDeleteSelected)(event); withPreventDefault(handleDeleteSelected)(event);
@ -220,7 +230,7 @@ export function OssFlow() {
onCreateBlock={handleCreateBlock} onCreateBlock={handleCreateBlock}
onCreateSchema={handleCreateSchema} onCreateSchema={handleCreateSchema}
onImportSchema={handleImportSchema} onImportSchema={handleImportSchema}
onCreateSynthesis={handleCreateOperation} onCreateSynthesis={handleCreateSynthesis}
onDelete={handleDeleteSelected} onDelete={handleDeleteSelected}
onResetPositions={resetGraph} onResetPositions={resetGraph}
openContextMenu={openContextMenu} openContextMenu={openContextMenu}

View File

@ -113,9 +113,8 @@ export function ToolbarOssGraph({
return ( return (
<div <div
className={cn( className={cn(
'cc-tab-tools flex flex-col items-center', 'grid justify-items-center', //
'rounded-b-2xl', 'rounded-b-2xl hover:bg-background backdrop-blur-xs',
'hover:bg-background backdrop-blur-xs',
className className
)} )}
{...restProps} {...restProps}

View File

@ -44,7 +44,7 @@ export function SchemasGuide({ schema }: SchemasGuideProps) {
<Tooltip <Tooltip
anchorSelect={`#${globalIDs.graph_schemas}`} anchorSelect={`#${globalIDs.graph_schemas}`}
place='right' place='right'
className='grid max-w-100 break-words text-base' className='z-topmost grid max-w-100 break-words text-base'
> >
<div className='inline-flex items-center gap-2'> <div className='inline-flex items-center gap-2'>
<span className='w-3 h-3 border rounded-full' style={{ backgroundColor: colorBgSchemas(0) }} /> <span className='w-3 h-3 border rounded-full' style={{ backgroundColor: colorBgSchemas(0) }} />

View File

@ -1,4 +1,5 @@
import { MiniButton } from '@/components/control'; import { MiniButton } from '@/components/control';
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
import { import {
IconGraphCollapse, IconGraphCollapse,
IconGraphCore, IconGraphCore,
@ -7,6 +8,7 @@ import {
IconGraphInverse, IconGraphInverse,
IconGraphMaximize, IconGraphMaximize,
IconGraphOutputs, IconGraphOutputs,
IconGraphSelection,
IconPredecessor, IconPredecessor,
IconReset IconReset
} from '@/components/icons'; } from '@/components/icons';
@ -31,6 +33,7 @@ export function ToolbarGraphSelection({
onChange, onChange,
...restProps ...restProps
}: ToolbarGraphSelectionProps) { }: ToolbarGraphSelectionProps) {
const menu = useDropdown();
const emptySelection = selected.length === 0; const emptySelection = selected.length === 0;
function handleSelectCore() { function handleSelectCore() {
@ -43,61 +46,77 @@ export function ToolbarGraphSelection({
} }
return ( return (
<div className={cn('cc-icons', className)} {...restProps}> <div className={cn('cc-icons items-center', className)} {...restProps}>
<MiniButton <MiniButton
title='Сбросить выделение' title='Сбросить выделение'
icon={<IconReset size='1.25rem' className='icon-primary' />} icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => onChange([])} onClick={() => onChange([])}
disabled={emptySelection} disabled={emptySelection}
/> />
<div ref={menu.ref} onBlur={menu.handleBlur} className='flex items-center relative'>
<MiniButton
title='Выделить...'
hideTitle={menu.isOpen}
icon={<IconGraphSelection size='1.25rem' className='icon-primary' />}
onClick={menu.toggle}
disabled={emptySelection}
/>
<Dropdown isOpen={menu.isOpen} className='-translate-x-1/2'>
<DropdownButton
text='Влияющие'
title='Выделить все влияющие'
icon={<IconGraphCollapse size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandAllInputs(selected)])}
disabled={emptySelection}
/>
<DropdownButton
text='Зависимые'
title='Выделить все зависимые'
icon={<IconGraphExpand size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandAllOutputs(selected)])}
disabled={emptySelection}
/>
<DropdownButton
text='Поставщики'
title='Выделить поставщиков'
icon={<IconGraphInputs size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandInputs(selected)])}
disabled={emptySelection}
/>
<DropdownButton
text='Потребители'
title='Выделить потребителей'
icon={<IconGraphOutputs size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandOutputs(selected)])}
disabled={emptySelection}
/>
<DropdownButton
text='Максимизация'
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
aria-label='Максимизация - дополнение выделения конституентами, зависимыми только от выделенных'
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
onClick={() => onChange(graph.maximizePart(selected))}
disabled={emptySelection}
/>
</Dropdown>
</div>
<MiniButton <MiniButton
title='Выделить все влияющие' title='Выделить ядро'
icon={<IconGraphCollapse size='1.25rem' className='icon-primary' />} icon={<IconGraphCore size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandAllInputs(selected)])} onClick={handleSelectCore}
disabled={emptySelection}
/> />
<MiniButton <MiniButton
title='Выделить все зависимые' title='Выделить собственные'
icon={<IconGraphExpand size='1.25rem' className='icon-primary' />} icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandAllOutputs(selected)])} onClick={handleSelectOwned}
disabled={emptySelection}
/>
<MiniButton
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
aria-label='Максимизация - дополнение выделения конституентами, зависимыми только от выделенных'
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
onClick={() => onChange(graph.maximizePart(selected))}
disabled={emptySelection}
/>
<MiniButton
title='Выделить поставщиков'
icon={<IconGraphInputs size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandInputs(selected)])}
disabled={emptySelection}
/>
<MiniButton
title='Выделить потребителей'
icon={<IconGraphOutputs size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...selected, ...graph.expandOutputs(selected)])}
disabled={emptySelection}
/> />
<MiniButton <MiniButton
title='Инвертировать' title='Инвертировать'
icon={<IconGraphInverse size='1.25rem' className='icon-primary' />} icon={<IconGraphInverse size='1.25rem' className='icon-primary' />}
onClick={() => onChange([...graph.nodes.keys()].filter(item => !selected.includes(item)))} onClick={() => onChange([...graph.nodes.keys()].filter(item => !selected.includes(item)))}
/> />
<MiniButton
title='Выделить ядро'
icon={<IconGraphCore size='1.25rem' className='icon-primary' />}
onClick={handleSelectCore}
/>
{isOwned ? (
<MiniButton
title='Выделить собственные'
icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
onClick={handleSelectOwned}
/>
) : null}
</div> </div>
); );
} }

View File

@ -3,7 +3,7 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { type Edge, MarkerType, type Node, useEdgesState, useNodesState, useOnSelectionChange } from 'reactflow'; import { type Edge, MarkerType, type Node, useEdgesState, useNodesState, useOnSelectionChange } from 'reactflow';
import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow'; import { DiagramFlow, useReactFlow } from '@/components/flow/diagram-flow';
import { useMainHeight } from '@/stores/app-layout'; import { useMainHeight } from '@/stores/app-layout';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { withPreventDefault } from '@/utils/utils'; import { withPreventDefault } from '@/utils/utils';
@ -12,10 +12,7 @@ import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
import { TGEdgeTypes } from '../../../components/term-graph/graph/tg-edge-types'; import { TGEdgeTypes } from '../../../components/term-graph/graph/tg-edge-types';
import { TGNodeTypes } from '../../../components/term-graph/graph/tg-node-types'; import { TGNodeTypes } from '../../../components/term-graph/graph/tg-node-types';
import { SelectColoring } from '../../../components/term-graph/select-coloring'; import { SelectColoring } from '../../../components/term-graph/select-coloring';
import { ToolbarFocusedCst } from '../../../components/term-graph/toolbar-focused-cst';
import { ToolbarGraphSelection } from '../../../components/toolbar-graph-selection';
import { applyLayout, type TGNodeData } from '../../../models/graph-api'; import { applyLayout, type TGNodeData } from '../../../models/graph-api';
import { isBasicConcept } from '../../../models/rsform-api';
import { useTermGraphStore } from '../../../stores/term-graph'; import { useTermGraphStore } from '../../../stores/term-graph';
import { useRSEdit } from '../rsedit-context'; import { useRSEdit } from '../rsedit-context';
@ -36,20 +33,9 @@ export const flowOptions = {
export function TGFlow() { export function TGFlow() {
const mainHeight = useMainHeight(); const mainHeight = useMainHeight();
const { fitView, viewportInitialized } = useReactFlow(); const { fitView, viewportInitialized } = useReactFlow();
const store = useStoreApi();
const { addSelectedNodes } = store.getState();
const isProcessing = useMutatingRSForm(); const isProcessing = useMutatingRSForm();
const { const { isContentEditable, schema, selected, setSelected, promptDeleteCst, focusCst, setFocus, navigateCst } =
isContentEditable, useRSEdit();
schema,
selected,
setSelected,
promptDeleteCst,
focusCst,
setFocus,
deselectAll,
navigateCst
} = useRSEdit();
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]); const [nodes, setNodes, onNodesChange] = useNodesState<Node>([]);
const [edges, setEdges] = useEdgesState<Edge>([]); const [edges, setEdges] = useEdgesState<Edge>([]);
@ -59,11 +45,8 @@ export function TGFlow() {
function onSelectionChange({ nodes }: { nodes: Node[] }) { function onSelectionChange({ nodes }: { nodes: Node[] }) {
const ids = nodes.map(node => Number(node.id)); const ids = nodes.map(node => Number(node.id));
if (ids.length === 0) {
deselectAll(); setSelected(prev => [...prev.filter(nodeID => !filteredGraph.hasNode(nodeID)), ...ids]);
} else {
setSelected(prev => [...prev.filter(nodeID => !filteredGraph.hasNode(nodeID)), ...ids]);
}
} }
useOnSelectionChange({ useOnSelectionChange({
onChange: onSelectionChange onChange: onSelectionChange
@ -130,11 +113,6 @@ export function TGFlow() {
); );
} }
function handleSetSelected(newSelection: number[]) {
setSelected(newSelection);
addSelectedNodes(newSelection.map(id => String(id)));
}
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) { function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (isProcessing) { if (isProcessing) {
return; return;
@ -166,26 +144,7 @@ export function TGFlow() {
return ( return (
<div className='relative' tabIndex={-1} onKeyDown={handleKeyDown}> <div className='relative' tabIndex={-1} onKeyDown={handleKeyDown}>
<div className='cc-tab-tools flex flex-col items-center rounded-b-2xl backdrop-blur-xs'> <ToolbarTermGraph className='cc-tab-tools' />
<ToolbarTermGraph />
{focusCst ? (
<ToolbarFocusedCst
focus={focusCst} //
resetFocus={() => setFocus(null)}
/>
) : (
<ToolbarGraphSelection
graph={schema.graph}
isCore={cstID => {
const cst = schema.cstByID.get(cstID);
return !!cst && isBasicConcept(cst.cst_type);
}}
isOwned={schema.inheritance.length > 0 ? cstID => !schema.cstByID.get(cstID)?.is_inherited : undefined}
value={selected}
onChange={handleSetSelected}
/>
)}
</div>
<div className='absolute z-pop top-24 sm:top-16 left-2 sm:left-3 w-54 flex flex-col pointer-events-none'> <div className='absolute z-pop top-24 sm:top-16 left-2 sm:left-3 w-54 flex flex-col pointer-events-none'>
<span className='px-2 pb-1 select-none whitespace-nowrap backdrop-blur-xs rounded-xl w-fit'> <span className='px-2 pb-1 select-none whitespace-nowrap backdrop-blur-xs rounded-xl w-fit'>

View File

@ -1,9 +1,12 @@
import { useReactFlow } from 'reactflow'; import { useReactFlow, useStoreApi } from 'reactflow';
import { HelpTopic } from '@/features/help'; import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components/badge-help'; import { BadgeHelp } from '@/features/help/components/badge-help';
import { type ILibraryItemReference } from '@/features/library'; import { type ILibraryItemReference } from '@/features/library';
import { MiniSelectorOSS } from '@/features/library/components/mini-selector-oss'; import { MiniSelectorOSS } from '@/features/library/components/mini-selector-oss';
import { ToolbarFocusedCst } from '@/features/rsform/components/term-graph/toolbar-focused-cst';
import { ToolbarGraphSelection } from '@/features/rsform/components/toolbar-graph-selection';
import { isBasicConcept } from '@/features/rsform/models/rsform-api';
import { MiniButton } from '@/components/control'; import { MiniButton } from '@/components/control';
import { import {
@ -18,6 +21,7 @@ import {
IconTextOff, IconTextOff,
IconTypeGraph IconTypeGraph
} from '@/components/icons'; } from '@/components/icons';
import { cn } from '@/components/utils';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
@ -28,17 +32,23 @@ import { useRSEdit } from '../rsedit-context';
import { flowOptions } from './tg-flow'; import { flowOptions } from './tg-flow';
export function ToolbarTermGraph() { interface ToolbarTermGraphProps {
className?: string;
}
export function ToolbarTermGraph({ className }: ToolbarTermGraphProps) {
const isProcessing = useMutatingRSForm(); const isProcessing = useMutatingRSForm();
const { const {
schema, // schema,
selected, selected,
setSelected,
setFocus, setFocus,
navigateOss, navigateOss,
isContentEditable, isContentEditable,
canDeleteSelected, canDeleteSelected,
createCst, createCst,
promptDeleteCst promptDeleteCst,
focusCst
} = useRSEdit(); } = useRSEdit();
const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph); const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph);
const showParams = useDialogsStore(state => state.showGraphParams); const showParams = useDialogsStore(state => state.showGraphParams);
@ -47,6 +57,8 @@ export function ToolbarTermGraph() {
const toggleClustering = useTermGraphStore(state => state.toggleClustering); const toggleClustering = useTermGraphStore(state => state.toggleClustering);
const { fitView } = useReactFlow(); const { fitView } = useReactFlow();
const store = useStoreApi();
const { addSelectedNodes } = store.getState();
function handleShowTypeGraph() { function handleShowTypeGraph() {
const typeInfo = schema.items.map(item => ({ const typeInfo = schema.items.map(item => ({
@ -86,69 +98,102 @@ export function ToolbarTermGraph() {
navigateOss(newValue.id, event.ctrlKey || event.metaKey); navigateOss(newValue.id, event.ctrlKey || event.metaKey);
} }
function handleSetSelected(newSelection: number[]) {
setSelected(newSelection);
addSelectedNodes(newSelection.map(id => String(id)));
}
return ( return (
<div className='cc-icons'> <div
{schema.oss.length > 0 ? <MiniSelectorOSS items={schema.oss} onSelect={handleSelectOss} /> : null} className={cn(
<MiniButton 'grid justify-items-center', //
title='Настройки фильтрации узлов и связей' 'rounded-b-2xl hover:bg-background backdrop-blur-xs',
icon={<IconFilter size='1.25rem' className='icon-primary' />} className
onClick={showParams} )}
/> >
<MiniButton <div className='cc-icons'>
title='Задать фокус конституенту' {schema.oss.length > 0 ? <MiniSelectorOSS items={schema.oss} onSelect={handleSelectOss} /> : null}
icon={<IconFocus size='1.25rem' className='icon-primary' />}
disabled={selected.length !== 1}
onClick={handleSetFocus}
/>
<MiniButton
title='Граф целиком'
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
onClick={handleFitView}
/>
<MiniButton
title={!filter.noText ? 'Скрыть текст' : 'Отобразить текст'}
icon={
!filter.noText ? (
<IconText size='1.25rem' className='icon-green' />
) : (
<IconTextOff size='1.25rem' className='icon-primary' />
)
}
onClick={toggleText}
/>
<MiniButton
title={!filter.foldDerived ? 'Скрыть порожденные' : 'Отобразить порожденные'}
icon={
!filter.foldDerived ? (
<IconClustering size='1.25rem' className='icon-green' />
) : (
<IconClusteringOff size='1.25rem' className='icon-primary' />
)
}
onClick={toggleClustering}
/>
{isContentEditable ? (
<MiniButton <MiniButton
title='Новая конституента' title='Настройки фильтрации узлов и связей'
icon={<IconNewItem size='1.25rem' className='icon-green' />} icon={<IconFilter size='1.25rem' className='icon-primary' />}
onClick={handleCreateCst} onClick={showParams}
disabled={isProcessing}
/> />
) : null}
{isContentEditable ? (
<MiniButton <MiniButton
title='Удалить выбранные' title='Задать фокус конституенту'
icon={<IconDestroy size='1.25rem' className='icon-red' />} icon={<IconFocus size='1.25rem' className='icon-primary' />}
onClick={handleDeleteCst} disabled={selected.length !== 1}
disabled={!canDeleteSelected || isProcessing} onClick={handleSetFocus}
/> />
) : null} <MiniButton
<MiniButton title='Граф целиком'
icon={<IconTypeGraph size='1.25rem' className='icon-primary' />} icon={<IconFitImage size='1.25rem' className='icon-primary' />}
title='Граф ступеней' onClick={handleFitView}
onClick={handleShowTypeGraph} />
/> <MiniButton
<BadgeHelp topic={HelpTopic.UI_GRAPH_TERM} contentClass='sm:max-w-160' offset={4} /> title={!filter.noText ? 'Скрыть текст' : 'Отобразить текст'}
icon={
!filter.noText ? (
<IconText size='1.25rem' className='icon-green' />
) : (
<IconTextOff size='1.25rem' className='icon-primary' />
)
}
onClick={toggleText}
/>
<MiniButton
title={!filter.foldDerived ? 'Скрыть порожденные' : 'Отобразить порожденные'}
icon={
!filter.foldDerived ? (
<IconClustering size='1.25rem' className='icon-green' />
) : (
<IconClusteringOff size='1.25rem' className='icon-primary' />
)
}
onClick={toggleClustering}
/>
<MiniButton
icon={<IconTypeGraph size='1.25rem' className='icon-primary' />}
title='Граф ступеней'
onClick={handleShowTypeGraph}
/>
<BadgeHelp topic={HelpTopic.UI_GRAPH_TERM} contentClass='sm:max-w-160' offset={4} />
</div>
<div className='cc-icons items-center'>
{focusCst ? (
<ToolbarFocusedCst
focus={focusCst} //
resetFocus={() => setFocus(null)}
/>
) : (
<ToolbarGraphSelection
graph={schema.graph}
isCore={cstID => {
const cst = schema.cstByID.get(cstID);
return !!cst && isBasicConcept(cst.cst_type);
}}
isOwned={schema.inheritance.length > 0 ? cstID => !schema.cstByID.get(cstID)?.is_inherited : undefined}
value={selected}
onChange={handleSetSelected}
/>
)}
{isContentEditable ? (
<MiniButton
title='Новая конституента'
icon={<IconNewItem size='1.25rem' className='icon-green' />}
onClick={handleCreateCst}
disabled={isProcessing}
/>
) : null}
{isContentEditable ? (
<MiniButton
title='Удалить выбранные'
icon={<IconDestroy size='1.25rem' className='icon-red' />}
onClick={handleDeleteCst}
disabled={!canDeleteSelected || isProcessing}
/>
) : null}
</div>
</div> </div>
); );
} }