F: Improve OSS UI
This commit is contained in:
parent
976ab669e7
commit
6b36a3fd41
|
@ -107,7 +107,7 @@ export function HelpOssGraph() {
|
|||
<kbd>Space</kbd> – перемещение экрана
|
||||
</li>
|
||||
<li>
|
||||
<kbd>Shift</kbd> – перемещение выделенных элементов в границах родителя
|
||||
<kbd>Shift</kbd> – перемещение в границах блока
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -31,10 +31,6 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
const hasFile = !!node.data.operation.result;
|
||||
const longLabel = node.data.label.length > LONG_LABEL_CHARS;
|
||||
|
||||
function handleTouchStart(event: React.TouchEvent) {
|
||||
console.log('handleTouchStart', event);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
@ -42,7 +38,6 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
'relative flex items-center justify-center p-[2px]',
|
||||
isChild && 'border-accent-orange'
|
||||
)}
|
||||
onTouchStart={handleTouchStart}
|
||||
>
|
||||
<div className='absolute z-pop top-0 right-0 flex flex-col gap-[4px] p-[2px]'>
|
||||
<Indicator
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { DiagramFlow, useReactFlow, useStoreApi } from '@/components/flow/diagram-flow';
|
||||
|
@ -8,7 +8,6 @@ import { useMainHeight } from '@/stores/app-layout';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { promptText } from '@/utils/labels';
|
||||
import { isIOS } from '@/utils/utils';
|
||||
|
||||
import { useDeleteBlock } from '../../../backend/use-delete-block';
|
||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||
|
@ -68,9 +67,6 @@ export function OssFlow() {
|
|||
const { isOpen: isContextMenuOpen, menuProps, openContextMenu, hideContextMenu } = useContextMenu();
|
||||
const { handleDragStart, handleDrag, handleDragStop } = useDragging({ hideContextMenu });
|
||||
|
||||
const longPressTimeout = useRef<NodeJS.Timeout | null>(null);
|
||||
const longPressTarget = useRef<EventTarget | null>(null);
|
||||
|
||||
function handleSavePositions() {
|
||||
void updateLayout({ itemID: schema.id, data: getLayout() });
|
||||
}
|
||||
|
@ -193,62 +189,23 @@ export function OssFlow() {
|
|||
openContextMenu(node, event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
function handleTouchStart(event: React.TouchEvent) {
|
||||
if (!isIOS() || event.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Long-press support for iOS/iPadOS
|
||||
const touch = event.touches[0];
|
||||
longPressTarget.current = touch.target;
|
||||
longPressTimeout.current = setTimeout(() => {
|
||||
let targetID = null;
|
||||
let element = touch.target as HTMLElement | null;
|
||||
while (element) {
|
||||
if (element?.getAttribute?.('data-id')) {
|
||||
targetID = element.getAttribute('data-id');
|
||||
break;
|
||||
}
|
||||
element = element.parentElement;
|
||||
}
|
||||
if (targetID) {
|
||||
const targetNode = nodes.find(node => node.id === targetID);
|
||||
if (targetNode) {
|
||||
openContextMenu(targetNode, touch.clientX, touch.clientY);
|
||||
}
|
||||
}
|
||||
}, PARAMETER.ossContextMenuDuration);
|
||||
}
|
||||
|
||||
function handleTouchEnd() {
|
||||
if (!isIOS()) return;
|
||||
if (longPressTimeout.current) {
|
||||
clearTimeout(longPressTimeout.current);
|
||||
longPressTimeout.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTouchMove() {
|
||||
if (!isIOS()) return;
|
||||
if (longPressTimeout.current) {
|
||||
clearTimeout(longPressTimeout.current);
|
||||
longPressTimeout.current = null;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div tabIndex={-1} className='relative' onMouseMove={showCoordinates ? handleMouseMove : undefined}>
|
||||
{showCoordinates ? <CoordinateDisplay mouseCoords={mouseCoords} className='absolute top-1 right-2' /> : null}
|
||||
|
||||
<ContextMenu isOpen={isContextMenuOpen} onHide={hideContextMenu} {...menuProps} />
|
||||
|
||||
<ToolbarOssGraph
|
||||
className='absolute z-pop top-8 right-1/2 translate-x-1/2'
|
||||
onCreateOperation={handleCreateOperation}
|
||||
onCreateBlock={handleCreateBlock}
|
||||
onDelete={handleDeleteSelected}
|
||||
onResetPositions={resetGraph}
|
||||
openContextMenu={openContextMenu}
|
||||
isContextMenuOpen={isContextMenuOpen}
|
||||
hideContextMenu={hideContextMenu}
|
||||
/>
|
||||
|
||||
<ContextMenu isOpen={isContextMenuOpen} onHide={hideContextMenu} {...menuProps} />
|
||||
|
||||
<DiagramFlow
|
||||
{...flowOptions}
|
||||
className={clsx(!containMovement && 'cursor-relocate')}
|
||||
|
@ -267,9 +224,6 @@ export function OssFlow() {
|
|||
onNodeDragStart={handleDragStart}
|
||||
onNodeDrag={handleDrag}
|
||||
onNodeDragStop={handleDragStop}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
onTouchMove={handleTouchMove}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { HelpTopic } from '@/features/help';
|
||||
import { BadgeHelp } from '@/features/help/components';
|
||||
import { type OssNode } from '@/features/oss/models/oss-layout';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import {
|
||||
IconConceptBlock,
|
||||
IconDestroy,
|
||||
IconEdit2,
|
||||
IconExecute,
|
||||
IconFitImage,
|
||||
IconNewItem,
|
||||
IconReset,
|
||||
|
@ -18,14 +20,11 @@ import {
|
|||
import { type Styling } from '@/components/props';
|
||||
import { cn } from '@/components/utils';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { prepareTooltip } from '@/utils/utils';
|
||||
import { isIOS, prepareTooltip } from '@/utils/utils';
|
||||
|
||||
import { OperationType } from '../../../backend/types';
|
||||
import { useExecuteOperation } from '../../../backend/use-execute-operation';
|
||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||
import { useUpdateLayout } from '../../../backend/use-update-layout';
|
||||
import { NodeType } from '../../../models/oss';
|
||||
import { LayoutManager } from '../../../models/oss-layout-api';
|
||||
import { useOssEdit } from '../oss-edit-context';
|
||||
|
||||
import { useOssFlow } from './oss-flow-context';
|
||||
|
@ -36,6 +35,10 @@ interface ToolbarOssGraphProps extends Styling {
|
|||
onCreateBlock: () => void;
|
||||
onDelete: () => void;
|
||||
onResetPositions: () => void;
|
||||
|
||||
isContextMenuOpen: boolean;
|
||||
openContextMenu: (node: OssNode, clientX: number, clientY: number) => void;
|
||||
hideContextMenu: () => void;
|
||||
}
|
||||
|
||||
export function ToolbarOssGraph({
|
||||
|
@ -43,12 +46,16 @@ export function ToolbarOssGraph({
|
|||
onCreateBlock,
|
||||
onDelete,
|
||||
onResetPositions,
|
||||
|
||||
isContextMenuOpen,
|
||||
openContextMenu,
|
||||
hideContextMenu,
|
||||
className,
|
||||
...restProps
|
||||
}: ToolbarOssGraphProps) {
|
||||
const { schema, selectedItems, isMutable, canDeleteOperation: canDelete } = useOssEdit();
|
||||
const isProcessing = useMutatingOss();
|
||||
const { resetView } = useOssFlow();
|
||||
const { resetView, nodes } = useOssFlow();
|
||||
const selectedOperation =
|
||||
selectedItems.length === 1 && selectedItems[0].nodeType === NodeType.OPERATION ? selectedItems[0] : null;
|
||||
const selectedBlock =
|
||||
|
@ -56,33 +63,9 @@ export function ToolbarOssGraph({
|
|||
const getLayout = useGetLayout();
|
||||
|
||||
const { updateLayout } = useUpdateLayout();
|
||||
const { executeOperation } = useExecuteOperation();
|
||||
|
||||
const showEditOperation = useDialogsStore(state => state.showEditOperation);
|
||||
const showEditBlock = useDialogsStore(state => state.showEditBlock);
|
||||
const showOssOptions = useDialogsStore(state => state.showOssOptions);
|
||||
|
||||
const readyForSynthesis = (() => {
|
||||
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
|
||||
return false;
|
||||
}
|
||||
if (selectedOperation.result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const argumentIDs = schema.graph.expandInputs([selectedOperation.id]);
|
||||
if (!argumentIDs || argumentIDs.length < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const argumentOperations = argumentIDs.map(id => schema.operationByID.get(id)!);
|
||||
if (argumentOperations.some(item => item.result === null)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
function handleShowOptions() {
|
||||
showOssOptions();
|
||||
}
|
||||
|
@ -91,27 +74,18 @@ export function ToolbarOssGraph({
|
|||
void updateLayout({ itemID: schema.id, data: getLayout() });
|
||||
}
|
||||
|
||||
function handleOperationExecute() {
|
||||
if (!readyForSynthesis || !selectedOperation) {
|
||||
function handleEditItem(event: React.MouseEvent<HTMLButtonElement>) {
|
||||
if (isContextMenuOpen) {
|
||||
hideContextMenu();
|
||||
return;
|
||||
}
|
||||
void executeOperation({
|
||||
itemID: schema.id, //
|
||||
data: { target: selectedOperation.id, layout: getLayout() }
|
||||
});
|
||||
}
|
||||
|
||||
function handleEditItem() {
|
||||
if (selectedOperation) {
|
||||
showEditOperation({
|
||||
manager: new LayoutManager(schema, getLayout()),
|
||||
target: selectedOperation
|
||||
});
|
||||
} else if (selectedBlock) {
|
||||
showEditBlock({
|
||||
manager: new LayoutManager(schema, getLayout()),
|
||||
target: selectedBlock
|
||||
});
|
||||
const nodeID = selectedOperation?.nodeID ?? selectedBlock?.nodeID;
|
||||
if (!nodeID) {
|
||||
return;
|
||||
}
|
||||
const node = nodes.find(node => node.id === nodeID);
|
||||
if (node) {
|
||||
openContextMenu(node, event.clientX, event.clientY);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,11 +127,12 @@ export function ToolbarOssGraph({
|
|||
disabled={isProcessing}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Новый блок', 'Ctrl + Shift + Q')}
|
||||
aria-label='Новый блок'
|
||||
icon={<IconConceptBlock size='1.25rem' className='icon-green' />}
|
||||
onClick={onCreateBlock}
|
||||
disabled={isProcessing}
|
||||
titleHtml={prepareTooltip('Редактировать выбранную', isIOS() ? '' : 'Правый клик')}
|
||||
hideTitle={isContextMenuOpen}
|
||||
aria-label='Редактировать выбранную'
|
||||
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
||||
onClick={handleEditItem}
|
||||
disabled={selectedItems.length !== 1 || isProcessing}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Новая операция', 'Ctrl + Q')}
|
||||
|
@ -167,18 +142,13 @@ export function ToolbarOssGraph({
|
|||
disabled={isProcessing}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Активировать операцию'
|
||||
icon={<IconExecute size='1.25rem' className='icon-green' />}
|
||||
onClick={handleOperationExecute}
|
||||
disabled={isProcessing || selectedItems.length !== 1 || !readyForSynthesis}
|
||||
/>
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Редактировать выбранную', 'Двойной клик')}
|
||||
aria-label='Редактировать выбранную'
|
||||
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
|
||||
onClick={handleEditItem}
|
||||
disabled={selectedItems.length !== 1 || isProcessing}
|
||||
titleHtml={prepareTooltip('Новый блок', 'Ctrl + Shift + Q')}
|
||||
aria-label='Новый блок'
|
||||
icon={<IconConceptBlock size='1.25rem' className='icon-green' />}
|
||||
onClick={onCreateBlock}
|
||||
disabled={isProcessing}
|
||||
/>
|
||||
|
||||
<MiniButton
|
||||
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
||||
aria-label='Удалить выбранную'
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
|
||||
import { IconChild, IconEdit2 } from '@/components/icons';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { useMutatingOss } from '../../backend/use-mutating-oss';
|
||||
|
||||
import { useOssEdit } from './oss-edit-context';
|
||||
|
||||
export function MenuEditOss() {
|
||||
const { isAnonymous } = useAuthSuspense();
|
||||
const menu = useDropdown();
|
||||
const { schema, isMutable } = useOssEdit();
|
||||
const isProcessing = useMutatingOss();
|
||||
|
||||
const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents);
|
||||
|
||||
function handleRelocate() {
|
||||
menu.hide();
|
||||
showRelocateConstituents({
|
||||
oss: schema,
|
||||
initialTarget: undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (isAnonymous) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} onBlur={menu.handleBlur} className='relative'>
|
||||
<MiniButton
|
||||
noHover
|
||||
noPadding
|
||||
title='Редактирование'
|
||||
hideTitle={menu.isOpen}
|
||||
className='h-full px-3 text-muted-foreground hover:text-primary cc-animate-color'
|
||||
icon={<IconEdit2 size='1.25rem' />}
|
||||
onClick={menu.toggle}
|
||||
/>
|
||||
<Dropdown isOpen={menu.isOpen} margin='mt-3'>
|
||||
<DropdownButton
|
||||
text='Конституенты'
|
||||
titleHtml='Перенос конституент</br>между схемами'
|
||||
aria-label='Перенос конституент между схемами'
|
||||
icon={<IconChild size='1rem' className='icon-green' />}
|
||||
disabled={isProcessing || !isMutable}
|
||||
onClick={handleRelocate}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -53,7 +53,7 @@ export function MenuMain() {
|
|||
title='Меню'
|
||||
hideTitle={menu.isOpen}
|
||||
icon={<IconMenu size='1.25rem' />}
|
||||
className='h-full pl-2 text-muted-foreground hover:text-primary cc-animate-color'
|
||||
className='h-full px-2 text-muted-foreground hover:text-primary cc-animate-color'
|
||||
onClick={menu.toggle}
|
||||
/>
|
||||
<Dropdown isOpen={menu.isOpen} margin='mt-3'>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import { useAuthSuspense } from '@/features/auth';
|
||||
import { MenuRole } from '@/features/library/components';
|
||||
|
||||
import { MenuEditOss } from './menu-edit-oss';
|
||||
import { MenuMain } from './menu-main';
|
||||
import { useOssEdit } from './oss-edit-context';
|
||||
|
||||
|
@ -14,8 +13,6 @@ export function MenuOssTabs() {
|
|||
<div className='flex border-r-2'>
|
||||
<MenuMain />
|
||||
|
||||
<MenuEditOss />
|
||||
|
||||
<MenuRole isOwned={isOwned} isEditor={!!user.id && schema.editors.includes(user.id)} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -11,7 +11,6 @@ export const PARAMETER = {
|
|||
notificationDelay: 300, // milliseconds delay for notifications
|
||||
zoomDuration: 500, // milliseconds animation duration
|
||||
navigationPopupDelay: 300, // milliseconds delay for navigation popup
|
||||
ossContextMenuDuration: 500, // milliseconds - duration of long-press to trigger context menu on iOS/iPadOS
|
||||
|
||||
moveDuration: 500, // milliseconds - duration of move animation
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user