F: Add space mode to ossFlow
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
Frontend CI / notify-failure (push) Blocked by required conditions

This commit is contained in:
Ivan 2025-04-29 14:29:28 +03:00
parent a618a2bc42
commit e6a5b49b7a
6 changed files with 93 additions and 24 deletions

View File

@ -3,12 +3,15 @@ import {
IconAnimation, IconAnimation,
IconAnimationOff, IconAnimationOff,
IconChild, IconChild,
IconConceptBlock,
IconConnect, IconConnect,
IconConsolidation, IconConsolidation,
IconCoordinates,
IconDestroy, IconDestroy,
IconEdit2, IconEdit2,
IconExecute, IconExecute,
IconFitImage, IconFitImage,
IconFixLayout,
IconGrid, IconGrid,
IconLineStraight, IconLineStraight,
IconLineWave, IconLineWave,
@ -16,7 +19,8 @@ import {
IconNewRSForm, IconNewRSForm,
IconReset, IconReset,
IconRSForm, IconRSForm,
IconSave IconSave,
IconSettings
} from '@/components/icons'; } from '@/components/icons';
import { LinkTopic } from '../../components/link-topic'; import { LinkTopic } from '../../components/link-topic';
@ -29,9 +33,19 @@ export function HelpOssGraph() {
<div className='flex flex-col sm:flex-row'> <div className='flex flex-col sm:flex-row'>
<div className='sm:w-56'> <div className='sm:w-56'>
<h1>Настройка графа</h1> <h1>Настройка графа</h1>
<li>
<IconReset className='inline-icon' /> Сбросить изменения
</li>
<li> <li>
<IconFitImage className='inline-icon' /> Вписать в экран <IconFitImage className='inline-icon' /> Вписать в экран
</li> </li>
<li>
<IconFixLayout className='inline-icon' /> Исправить расположения
</li>
<li>
<IconSettings className='inline-icon' /> Диалог настроек
</li>
<li> <li>
<IconGrid className='inline-icon' /> Отображение сетки <IconGrid className='inline-icon' /> Отображение сетки
</li> </li>
@ -43,6 +57,9 @@ export function HelpOssGraph() {
<IconAnimation className='inline-icon' /> <IconAnimation className='inline-icon' />
<IconAnimationOff className='inline-icon' /> Анимация <IconAnimationOff className='inline-icon' /> Анимация
</li> </li>
<li>
<IconCoordinates className='inline-icon' /> Отображение координат
</li>
<li>черта сверху - Загрузка</li> <li>черта сверху - Загрузка</li>
<li> <li>
черта слева - КС <LinkTopic text='внешняя' topic={HelpTopic.CC_OSS} /> черта слева - КС <LinkTopic text='внешняя' topic={HelpTopic.CC_OSS} />
@ -63,11 +80,14 @@ export function HelpOssGraph() {
<kbd>Двойной клик</kbd> переход к связанной <LinkTopic text='КС' topic={HelpTopic.CC_SYSTEM} /> <kbd>Двойной клик</kbd> переход к связанной <LinkTopic text='КС' topic={HelpTopic.CC_SYSTEM} />
</li> </li>
<li> <li>
<IconEdit2 className='inline-icon' /> Редактирование операции <IconConceptBlock className='inline-icon icon-green' /> Новый блок
</li> </li>
<li> <li>
<IconNewItem className='inline-icon icon-green' /> Новая операция <IconNewItem className='inline-icon icon-green' /> Новая операция
</li> </li>
<li>
<IconEdit2 className='inline-icon' /> Редактирование узла
</li>
<li> <li>
<IconDestroy className='inline-icon icon-red' /> <kbd>Delete</kbd> удалить выбранные <IconDestroy className='inline-icon icon-red' /> <kbd>Delete</kbd> удалить выбранные
</li> </li>
@ -80,10 +100,13 @@ export function HelpOssGraph() {
<div className='sm:w-56'> <div className='sm:w-56'>
<h1>Общие</h1> <h1>Общие</h1>
<li> <li>
<IconReset className='inline-icon' /> Сбросить изменения <IconSave className='inline-icon' /> Сохранить положения
</li> </li>
<li> <li>
<IconSave className='inline-icon' /> Сохранить положения <kbd>Space</kbd> перемещение экрана
</li>
<li>
<kbd>Shift</kbd> перемещение выделенных элементов в границах родителя
</li> </li>
</div> </div>

View File

@ -97,8 +97,6 @@ export class LayoutManager {
: Math.max(bottom, block.y + block.height + MIN_DISTANCE); : Math.max(bottom, block.y + block.height + MIN_DISTANCE);
} }
console.log('left, top, right, bottom', left, top, right, bottom);
for (const operation of operation_nodes) { for (const operation of operation_nodes) {
left = !left ? operation.x - MIN_DISTANCE : Math.min(left, operation.x - MIN_DISTANCE); left = !left ? operation.x - MIN_DISTANCE : Math.min(left, operation.x - MIN_DISTANCE);
top = !top ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE); top = !top ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE);

View File

@ -28,7 +28,7 @@ export function BlockNode(node: BlockInternalNode) {
return ( return (
<> <>
<NodeResizeControl minWidth={BLOCK_NODE_MIN_WIDTH} minHeight={BLOCK_NODE_MIN_HEIGHT}> <NodeResizeControl minWidth={BLOCK_NODE_MIN_WIDTH} minHeight={BLOCK_NODE_MIN_HEIGHT}>
<IconResize size={8} className='absolute bottom-[2px] right-[2px]' /> <IconResize size={8} className='absolute bottom-[2px] right-[2px] cc-graph-interactive' />
</NodeResizeControl> </NodeResizeControl>
{showCoordinates ? ( {showCoordinates ? (
<div <div
@ -49,10 +49,10 @@ export function BlockNode(node: BlockInternalNode) {
isChild && 'border-accent-orange' isChild && 'border-accent-orange'
)} )}
> >
<div className='absolute top-0 left-0 w-full h-2 pointer-events-auto cursor-pointer' /> <div className='absolute top-0 left-0 w-full h-2 cc-graph-interactive cursor-pointer' />
<div className='absolute top-0 right-0 h-full w-2 pointer-events-auto cursor-pointer' /> <div className='absolute top-0 right-0 h-full w-2 cc-graph-interactive cursor-pointer' />
<div className='absolute bottom-0 right-0 w-full h-2 pointer-events-auto cursor-pointer' /> <div className='absolute bottom-0 right-0 w-full h-2 cc-graph-interactive cursor-pointer' />
<div className='absolute bottom-0 left-0 h-full w-2 pointer-events-auto cursor-pointer' /> <div className='absolute bottom-0 left-0 h-full w-2 cc-graph-interactive cursor-pointer' />
<div <div
className={clsx( className={clsx(
@ -60,7 +60,7 @@ export function BlockNode(node: BlockInternalNode) {
'px-2', 'px-2',
'bg-background rounded-lg', 'bg-background rounded-lg',
'text-[18px]/[20px] line-clamp-2 text-center text-ellipsis', 'text-[18px]/[20px] line-clamp-2 text-center text-ellipsis',
'pointer-events-auto cursor-pointer' 'cc-graph-interactive cursor-pointer'
)} )}
data-tooltip-id={globalIDs.operation_tooltip} data-tooltip-id={globalIDs.operation_tooltip}
data-tooltip-hidden={node.dragging} data-tooltip-hidden={node.dragging}

View File

@ -46,6 +46,7 @@ export function OssFlow() {
const { deleteBlock } = useDeleteBlock(); const { deleteBlock } = useDeleteBlock();
const [mouseCoords, setMouseCoords] = useState<Position2D>({ x: 0, y: 0 }); const [mouseCoords, setMouseCoords] = useState<Position2D>({ x: 0, y: 0 });
const [spacePressed, setSpacePressed] = useState(false);
const showCreateOperation = useDialogsStore(state => state.showCreateOperation); const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
const showCreateBlock = useDialogsStore(state => state.showCreateBlock); const showCreateBlock = useDialogsStore(state => state.showCreateBlock);
@ -128,6 +129,12 @@ export function OssFlow() {
} }
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) { function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.code === 'Space') {
event.preventDefault();
event.stopPropagation();
setSpacePressed(true);
return;
}
if (isProcessing) { if (isProcessing) {
return; return;
} }
@ -164,6 +171,12 @@ export function OssFlow() {
} }
} }
function handleKeyUp(event: React.KeyboardEvent<HTMLDivElement>) {
if (event.code === 'Space') {
setSpacePressed(false);
}
}
function handleMouseMove(event: React.MouseEvent<HTMLDivElement>) { function handleMouseMove(event: React.MouseEvent<HTMLDivElement>) {
const targetPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY }); const targetPosition = screenToFlowPosition({ x: event.clientX, y: event.clientY });
setMouseCoords(targetPosition); setMouseCoords(targetPosition);
@ -174,6 +187,7 @@ export function OssFlow() {
tabIndex={-1} tabIndex={-1}
className='relative' className='relative'
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onMouseMove={showCoordinates ? handleMouseMove : undefined} onMouseMove={showCoordinates ? handleMouseMove : undefined}
> >
{showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null} {showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null}
@ -188,14 +202,18 @@ export function OssFlow() {
<ContextMenu isOpen={isContextMenuOpen} onHide={hideContextMenu} {...menuProps} /> <ContextMenu isOpen={isContextMenuOpen} onHide={hideContextMenu} {...menuProps} />
<div <div
className={clsx('relative w-[100vw] cc-mask-sides', !containMovement && 'cursor-relocate')} className={clsx(
'relative w-[100vw] cc-mask-sides',
spacePressed ? 'space-mode' : '',
!containMovement && 'cursor-relocate'
)}
style={{ height: mainHeight, fontFamily: 'Rubik' }} style={{ height: mainHeight, fontFamily: 'Rubik' }}
> >
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={spacePressed ? undefined : onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={spacePressed ? undefined : onEdgesChange}
edgesFocusable={false} edgesFocusable={false}
nodesFocusable={false} nodesFocusable={false}
fitView fitView
@ -206,15 +224,16 @@ export function OssFlow() {
snapToGrid={true} snapToGrid={true}
snapGrid={[GRID_SIZE, GRID_SIZE]} snapGrid={[GRID_SIZE, GRID_SIZE]}
onClick={hideContextMenu} onClick={hideContextMenu}
onNodeDoubleClick={handleNodeDoubleClick} onNodeDoubleClick={spacePressed ? undefined : handleNodeDoubleClick}
onNodeContextMenu={handleContextMenu} onNodeContextMenu={handleContextMenu}
onContextMenu={event => { onContextMenu={event => {
event.preventDefault(); event.preventDefault();
hideContextMenu(); hideContextMenu();
}} }}
onNodeDragStart={handleDragStart} nodesDraggable={!spacePressed}
onNodeDrag={handleDrag} onNodeDragStart={spacePressed ? undefined : handleDragStart}
onNodeDragStop={handleDragStop} onNodeDrag={spacePressed ? undefined : handleDrag}
onNodeDragStop={spacePressed ? undefined : handleDragStop}
> >
{showGrid ? <Background gap={GRID_SIZE} /> : null} {showGrid ? <Background gap={GRID_SIZE} /> : null}
</ReactFlow> </ReactFlow>

View File

@ -6,6 +6,8 @@
/* stylelint-disable selector-class-pattern */ /* stylelint-disable selector-class-pattern */
.react-flow__handle { .react-flow__handle {
pointer-events: auto;
z-index: var(--z-index-navigation); z-index: var(--z-index-navigation);
cursor: default !important; cursor: default !important;
border-radius: 9999px; border-radius: 9999px;
@ -16,6 +18,10 @@
.selected & { .selected & {
border-color: var(--color-muted-foreground); border-color: var(--color-muted-foreground);
} }
.space-mode & {
pointer-events: none !important;
}
} }
.react-flow__resize-control.handle { .react-flow__resize-control.handle {
@ -31,14 +37,26 @@
&:hover { &:hover {
color: var(--color-foreground); color: var(--color-foreground);
} }
.space-mode & {
pointer-events: none !important;
}
} }
.react-flow__pane { .react-flow__pane {
cursor: default; cursor: inherit;
.space-mode & {
cursor: grab;
&.dragging {
cursor: grabbing;
}
}
} }
.react-flow__edge { .react-flow__edge {
cursor: default; cursor: inherit;
} }
.react-flow__attribution { .react-flow__attribution {
@ -52,8 +70,13 @@
} }
[class*='react-flow__node-'] { [class*='react-flow__node-'] {
.space-mode & {
box-shadow: none;
pointer-events: none !important;
}
&:hover:not(.selected) { &:hover:not(.selected) {
box-shadow: 0 0 0 2px var(--color-selected) !important; box-shadow: 0 0 0 2px var(--color-selected);
} }
} }
@ -138,8 +161,6 @@
.react-flow__node-input, .react-flow__node-input,
.react-flow__node-synthesis { .react-flow__node-synthesis {
cursor: pointer;
border-radius: 5px; border-radius: 5px;
border-width: 0; border-width: 0;

View File

@ -205,3 +205,11 @@
--tw-duration: var(--duration-move); --tw-duration: var(--duration-move);
transition-duration: var(--duration-move); transition-duration: var(--duration-move);
} }
@utility cc-graph-interactive {
pointer-events: auto;
.space-mode & {
pointer-events: none;
}
}