mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
F: Improve oss UI
This commit is contained in:
parent
c25f22f340
commit
8cf3a0efe1
|
@ -123,6 +123,11 @@ const DlgEditBlock = React.lazy(() =>
|
||||||
default: module.DlgEditBlock
|
default: module.DlgEditBlock
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
const DlgOssSettings = React.lazy(() =>
|
||||||
|
import('@/features/oss/dialogs/dlg-oss-settings').then(module => ({
|
||||||
|
default: module.DlgOssSettings
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
export const GlobalDialogs = () => {
|
export const GlobalDialogs = () => {
|
||||||
const active = useDialogsStore(state => state.active);
|
const active = useDialogsStore(state => state.active);
|
||||||
|
@ -155,6 +160,8 @@ export const GlobalDialogs = () => {
|
||||||
return <DlgEditWordForms />;
|
return <DlgEditWordForms />;
|
||||||
case DialogType.INLINE_SYNTHESIS:
|
case DialogType.INLINE_SYNTHESIS:
|
||||||
return <DlgInlineSynthesis />;
|
return <DlgInlineSynthesis />;
|
||||||
|
case DialogType.OSS_SETTINGS:
|
||||||
|
return <DlgOssSettings />;
|
||||||
case DialogType.SHOW_AST:
|
case DialogType.SHOW_AST:
|
||||||
return <DlgShowAST />;
|
return <DlgShowAST />;
|
||||||
case DialogType.SHOW_TYPE_GRAPH:
|
case DialogType.SHOW_TYPE_GRAPH:
|
||||||
|
|
|
@ -16,7 +16,7 @@ export { FiEdit as IconEdit2 } from 'react-icons/fi';
|
||||||
export { BiSearchAlt2 as IconSearch } from 'react-icons/bi';
|
export { BiSearchAlt2 as IconSearch } from 'react-icons/bi';
|
||||||
export { BiDownload as IconDownload } from 'react-icons/bi';
|
export { BiDownload as IconDownload } from 'react-icons/bi';
|
||||||
export { BiUpload as IconUpload } from 'react-icons/bi';
|
export { BiUpload as IconUpload } from 'react-icons/bi';
|
||||||
export { BiCog as IconSettings } from 'react-icons/bi';
|
export { LuSettings as IconSettings } from 'react-icons/lu';
|
||||||
export { TbEye as IconShow } from 'react-icons/tb';
|
export { TbEye as IconShow } from 'react-icons/tb';
|
||||||
export { TbEyeX as IconHide } from 'react-icons/tb';
|
export { TbEyeX as IconHide } from 'react-icons/tb';
|
||||||
export { BiShareAlt as IconShare } from 'react-icons/bi';
|
export { BiShareAlt as IconShare } from 'react-icons/bi';
|
||||||
|
@ -141,6 +141,7 @@ export { GrConnect as IconConnect } from 'react-icons/gr';
|
||||||
export { BiPlayCircle as IconExecute } from 'react-icons/bi';
|
export { BiPlayCircle as IconExecute } from 'react-icons/bi';
|
||||||
|
|
||||||
// ======== Graph UI =======
|
// ======== Graph UI =======
|
||||||
|
export { LuLayoutDashboard as IconFixLayout } from 'react-icons/lu';
|
||||||
export { BiCollapse as IconGraphCollapse } from 'react-icons/bi';
|
export { BiCollapse as IconGraphCollapse } from 'react-icons/bi';
|
||||||
export { BiExpand as IconGraphExpand } from 'react-icons/bi';
|
export { BiExpand as IconGraphExpand } from 'react-icons/bi';
|
||||||
export { LuMaximize as IconGraphMaximize } from 'react-icons/lu';
|
export { LuMaximize as IconGraphMaximize } from 'react-icons/lu';
|
||||||
|
|
|
@ -14,10 +14,13 @@ export interface CheckboxProps extends Omit<Button, 'value' | 'onClick' | 'onCha
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|
||||||
/** Current value - `true` or `false`. */
|
/** Current value - `true` or `false`. */
|
||||||
value?: boolean;
|
value: boolean;
|
||||||
|
|
||||||
/** Callback to set the `value`. */
|
/** Callback to set the `value`. */
|
||||||
onChange?: (newValue: boolean) => void;
|
onChange?: (newValue: boolean) => void;
|
||||||
|
|
||||||
|
/** Custom icon to display next instead of checkbox. */
|
||||||
|
customIcon?: (checked?: boolean) => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -31,6 +34,7 @@ export function Checkbox({
|
||||||
hideTitle,
|
hideTitle,
|
||||||
className,
|
className,
|
||||||
value,
|
value,
|
||||||
|
customIcon,
|
||||||
onChange,
|
onChange,
|
||||||
...restProps
|
...restProps
|
||||||
}: CheckboxProps) {
|
}: CheckboxProps) {
|
||||||
|
@ -63,6 +67,9 @@ export function Checkbox({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
|
{customIcon ? (
|
||||||
|
customIcon(value)
|
||||||
|
) : (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-4 h-4', //
|
'w-4 h-4', //
|
||||||
|
@ -72,6 +79,7 @@ export function Checkbox({
|
||||||
>
|
>
|
||||||
{value ? <CheckboxChecked /> : null}
|
{value ? <CheckboxChecked /> : null}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{label ? <span className={clsx('text-start text-sm whitespace-nowrap select-text', cursor)}>{label}</span> : null}
|
{label ? <span className={clsx('text-start text-sm whitespace-nowrap select-text', cursor)}>{label}</span> : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IconAnimation,
|
||||||
|
IconAnimationOff,
|
||||||
|
IconCoordinates,
|
||||||
|
IconGrid,
|
||||||
|
IconLineStraight,
|
||||||
|
IconLineWave
|
||||||
|
} from '@/components/icons';
|
||||||
|
import { Checkbox } from '@/components/input';
|
||||||
|
import { ModalView } from '@/components/modal';
|
||||||
|
|
||||||
|
import { useOSSGraphStore } from '../stores/oss-graph';
|
||||||
|
|
||||||
|
const ICON_SIZE = '1.5rem';
|
||||||
|
|
||||||
|
export function DlgOssSettings() {
|
||||||
|
const showGrid = useOSSGraphStore(state => state.showGrid);
|
||||||
|
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
|
||||||
|
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
|
||||||
|
const edgeStraight = useOSSGraphStore(state => state.edgeStraight);
|
||||||
|
const toggleShowGrid = useOSSGraphStore(state => state.toggleShowGrid);
|
||||||
|
const toggleShowCoordinates = useOSSGraphStore(state => state.toggleShowCoordinates);
|
||||||
|
const toggleEdgeAnimate = useOSSGraphStore(state => state.toggleEdgeAnimate);
|
||||||
|
const toggleEdgeStraight = useOSSGraphStore(state => state.toggleEdgeStraight);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalView header='Настройки отображения' className='cc-column justify-between px-6 pb-3 w-100'>
|
||||||
|
<Checkbox
|
||||||
|
value={showCoordinates}
|
||||||
|
onChange={toggleShowCoordinates}
|
||||||
|
aria-label='Переключатель отображения координат'
|
||||||
|
label={`Координаты узлов: ${showCoordinates ? 'Вкл' : 'Выкл'}`}
|
||||||
|
customIcon={checked => <IconCoordinates size={ICON_SIZE} className={checked ? 'icon-green' : 'icon-primary'} />}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
value={showGrid}
|
||||||
|
onChange={toggleShowGrid}
|
||||||
|
aria-label='Переключатель отображения сетки'
|
||||||
|
label={`Отображение сетки: ${showGrid ? 'Вкл' : 'Выкл'}`}
|
||||||
|
customIcon={checked => <IconGrid size={ICON_SIZE} className={checked ? 'icon-green' : 'icon-primary'} />}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
value={edgeAnimate}
|
||||||
|
onChange={toggleEdgeAnimate}
|
||||||
|
aria-label='Переключатель анимации связей'
|
||||||
|
label={`Анимация связей: ${edgeAnimate ? 'Вкл' : 'Выкл'}`}
|
||||||
|
customIcon={checked =>
|
||||||
|
checked ? (
|
||||||
|
<IconAnimation size={ICON_SIZE} className='icon-primary' />
|
||||||
|
) : (
|
||||||
|
<IconAnimationOff size={ICON_SIZE} className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
value={edgeStraight}
|
||||||
|
onChange={toggleEdgeStraight}
|
||||||
|
aria-label='Переключатель формы связей'
|
||||||
|
label={`Связи: ${edgeStraight ? 'Прямые' : 'Безье'}`}
|
||||||
|
customIcon={checked =>
|
||||||
|
checked ? (
|
||||||
|
<IconLineStraight size={ICON_SIZE} className='icon-primary' />
|
||||||
|
) : (
|
||||||
|
<IconLineWave size={ICON_SIZE} className='icon-primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ModalView>
|
||||||
|
);
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ export function BlockNode(node: BlockInternalNode) {
|
||||||
'cc-node-block h-full w-full',
|
'cc-node-block h-full w-full',
|
||||||
isDragging && isParent && dropTarget !== node.data.block.id && 'border-destructive',
|
isDragging && isParent && dropTarget !== node.data.block.id && 'border-destructive',
|
||||||
((isParent && !isDragging) || dropTarget === node.data.block.id) && 'border-primary',
|
((isParent && !isDragging) || dropTarget === node.data.block.id) && 'border-primary',
|
||||||
isChild && 'border-accent-orange50'
|
isChild && 'border-accent-orange'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -40,7 +40,7 @@ export function NodeCore({ node }: NodeCoreProps) {
|
||||||
className={cn(
|
className={cn(
|
||||||
'cc-node-operation h-[40px] w-[150px]',
|
'cc-node-operation h-[40px] w-[150px]',
|
||||||
'relative flex items-center justify-center p-[2px]',
|
'relative flex items-center justify-center p-[2px]',
|
||||||
isChild && 'border-accent-orange50!'
|
isChild && 'border-accent-orange'
|
||||||
)}
|
)}
|
||||||
data-tooltip-id={globalIDs.operation_tooltip}
|
data-tooltip-id={globalIDs.operation_tooltip}
|
||||||
data-tooltip-hidden={node.dragging}
|
data-tooltip-hidden={node.dragging}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { useDeleteBlock } from '@/features/oss/backend/use-delete-block';
|
||||||
import { useMoveItems } from '@/features/oss/backend/use-move-items';
|
import { useMoveItems } from '@/features/oss/backend/use-move-items';
|
||||||
import { type IOperationSchema } from '@/features/oss/models/oss';
|
import { type IOperationSchema } from '@/features/oss/models/oss';
|
||||||
|
|
||||||
|
import { useThrottleCallback } from '@/hooks/use-throttle-callback';
|
||||||
import { useMainHeight } from '@/stores/app-layout';
|
import { useMainHeight } from '@/stores/app-layout';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
@ -42,6 +43,8 @@ const ZOOM_MIN = 0.5;
|
||||||
const Z_BLOCK = 1;
|
const Z_BLOCK = 1;
|
||||||
const Z_SCHEMA = 10;
|
const Z_SCHEMA = 10;
|
||||||
|
|
||||||
|
const DRAG_THROTTLE_DELAY = 50; // ms
|
||||||
|
|
||||||
export const VIEW_PADDING = 0.2;
|
export const VIEW_PADDING = 0.2;
|
||||||
|
|
||||||
export function OssFlow() {
|
export function OssFlow() {
|
||||||
|
@ -269,7 +272,7 @@ export function OssFlow() {
|
||||||
|
|
||||||
function determineDropTarget(event: React.MouseEvent): number | null {
|
function determineDropTarget(event: React.MouseEvent): number | null {
|
||||||
const mousePosition = screenToFlowPosition({ x: event.clientX, y: event.clientY });
|
const mousePosition = screenToFlowPosition({ x: event.clientX, y: event.clientY });
|
||||||
const blocks = getIntersectingNodes({
|
let blocks = getIntersectingNodes({
|
||||||
x: mousePosition.x,
|
x: mousePosition.x,
|
||||||
y: mousePosition.y,
|
y: mousePosition.y,
|
||||||
width: 1,
|
width: 1,
|
||||||
|
@ -283,6 +286,12 @@ export function OssFlow() {
|
||||||
if (blocks.length === 0) {
|
if (blocks.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const successors = schema.hierarchy.expandAllOutputs([...selected]).filter(id => id < 0);
|
||||||
|
blocks = blocks.filter(block => !successors.includes(-block.id));
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (blocks.length === 1) {
|
if (blocks.length === 1) {
|
||||||
return blocks[0].id;
|
return blocks[0].id;
|
||||||
}
|
}
|
||||||
|
@ -317,13 +326,13 @@ export function OssFlow() {
|
||||||
setIsContextMenuOpen(false);
|
setIsContextMenuOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDrag(event: React.MouseEvent) {
|
const handleDrag = useThrottleCallback((event: React.MouseEvent) => {
|
||||||
if (containMovement) {
|
if (containMovement) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
setDropTarget(determineDropTarget(event));
|
setDropTarget(determineDropTarget(event));
|
||||||
}
|
}, DRAG_THROTTLE_DELAY);
|
||||||
|
|
||||||
function handleDragStop(event: React.MouseEvent, target: Node) {
|
function handleDragStop(event: React.MouseEvent, target: Node) {
|
||||||
if (containMovement) {
|
if (containMovement) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import { useReactFlow } from 'reactflow';
|
import { useReactFlow } from 'reactflow';
|
||||||
|
|
||||||
import { HelpTopic } from '@/features/help';
|
import { HelpTopic } from '@/features/help';
|
||||||
|
@ -9,20 +10,16 @@ import { useUpdateLayout } from '@/features/oss/backend/use-update-layout';
|
||||||
|
|
||||||
import { MiniButton } from '@/components/control';
|
import { MiniButton } from '@/components/control';
|
||||||
import {
|
import {
|
||||||
IconAnimation,
|
|
||||||
IconAnimationOff,
|
|
||||||
IconConceptBlock,
|
IconConceptBlock,
|
||||||
IconCoordinates,
|
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconEdit2,
|
IconEdit2,
|
||||||
IconExecute,
|
IconExecute,
|
||||||
IconFitImage,
|
IconFitImage,
|
||||||
IconGrid,
|
IconFixLayout,
|
||||||
IconLineStraight,
|
|
||||||
IconLineWave,
|
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconReset,
|
IconReset,
|
||||||
IconSave
|
IconSave,
|
||||||
|
IconSettings
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
import { type Styling } from '@/components/props';
|
import { type Styling } from '@/components/props';
|
||||||
import { cn } from '@/components/utils';
|
import { cn } from '@/components/utils';
|
||||||
|
@ -32,7 +29,6 @@ import { prepareTooltip } from '@/utils/utils';
|
||||||
|
|
||||||
import { OperationType } from '../../../backend/types';
|
import { OperationType } from '../../../backend/types';
|
||||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||||
import { useOSSGraphStore } from '../../../stores/oss-graph';
|
|
||||||
import { useOssEdit } from '../oss-edit-context';
|
import { useOssEdit } from '../oss-edit-context';
|
||||||
|
|
||||||
import { VIEW_PADDING } from './oss-flow';
|
import { VIEW_PADDING } from './oss-flow';
|
||||||
|
@ -60,20 +56,12 @@ export function ToolbarOssGraph({
|
||||||
const selectedBlock = selected.length !== 1 ? null : schema.blockByID.get(-selected[0]) ?? null;
|
const selectedBlock = selected.length !== 1 ? null : schema.blockByID.get(-selected[0]) ?? null;
|
||||||
const getLayout = useGetLayout();
|
const getLayout = useGetLayout();
|
||||||
|
|
||||||
const showGrid = useOSSGraphStore(state => state.showGrid);
|
|
||||||
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
|
|
||||||
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
|
|
||||||
const edgeStraight = useOSSGraphStore(state => state.edgeStraight);
|
|
||||||
const toggleShowGrid = useOSSGraphStore(state => state.toggleShowGrid);
|
|
||||||
const toggleShowCoordinates = useOSSGraphStore(state => state.toggleShowCoordinates);
|
|
||||||
const toggleEdgeAnimate = useOSSGraphStore(state => state.toggleEdgeAnimate);
|
|
||||||
const toggleEdgeStraight = useOSSGraphStore(state => state.toggleEdgeStraight);
|
|
||||||
|
|
||||||
const { updateLayout } = useUpdateLayout();
|
const { updateLayout } = useUpdateLayout();
|
||||||
const { executeOperation } = useExecuteOperation();
|
const { executeOperation } = useExecuteOperation();
|
||||||
|
|
||||||
const showEditOperation = useDialogsStore(state => state.showEditOperation);
|
const showEditOperation = useDialogsStore(state => state.showEditOperation);
|
||||||
const showEditBlock = useDialogsStore(state => state.showEditBlock);
|
const showEditBlock = useDialogsStore(state => state.showEditBlock);
|
||||||
|
const showOssOptions = useDialogsStore(state => state.showOssOptions);
|
||||||
|
|
||||||
const readyForSynthesis = (() => {
|
const readyForSynthesis = (() => {
|
||||||
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
|
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
|
||||||
|
@ -100,6 +88,15 @@ export function ToolbarOssGraph({
|
||||||
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
|
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFixLayout() {
|
||||||
|
// TODO: implement layout algorithm
|
||||||
|
toast.info('Еще не реализовано');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleShowOptions() {
|
||||||
|
showOssOptions();
|
||||||
|
}
|
||||||
|
|
||||||
function handleSavePositions() {
|
function handleSavePositions() {
|
||||||
void updateLayout({ itemID: schema.id, data: getLayout() });
|
void updateLayout({ itemID: schema.id, data: getLayout() });
|
||||||
}
|
}
|
||||||
|
@ -152,46 +149,15 @@ export function ToolbarOssGraph({
|
||||||
onClick={handleFitView}
|
onClick={handleFitView}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title={showGrid ? 'Скрыть сетку' : 'Отобразить сетку'}
|
title='Исправить позиции узлов'
|
||||||
aria-label='Переключатель отображения сетки'
|
icon={<IconFixLayout size='1.25rem' className='icon-primary' />}
|
||||||
icon={
|
onClick={handleFixLayout}
|
||||||
showGrid ? (
|
disabled={selected.length > 1 || selected[0] > 0}
|
||||||
<IconGrid size='1.25rem' className='icon-green' />
|
|
||||||
) : (
|
|
||||||
<IconGrid size='1.25rem' className='icon-primary' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={toggleShowGrid}
|
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title={edgeStraight ? 'Связи: прямые' : 'Связи: безье'}
|
title='Настройки отображения'
|
||||||
aria-label='Переключатель формы связей'
|
icon={<IconSettings size='1.25rem' className='icon-primary' />}
|
||||||
icon={
|
onClick={handleShowOptions}
|
||||||
edgeStraight ? (
|
|
||||||
<IconLineStraight size='1.25rem' className='icon-primary' />
|
|
||||||
) : (
|
|
||||||
<IconLineWave size='1.25rem' className='icon-primary' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={toggleEdgeStraight}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
title={edgeAnimate ? 'Анимация: вкл' : 'Анимация: выкл'}
|
|
||||||
aria-label='Переключатель анимации связей'
|
|
||||||
icon={
|
|
||||||
edgeAnimate ? (
|
|
||||||
<IconAnimation size='1.25rem' className='icon-primary' />
|
|
||||||
) : (
|
|
||||||
<IconAnimationOff size='1.25rem' className='icon-primary' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={toggleEdgeAnimate}
|
|
||||||
/>
|
|
||||||
<MiniButton
|
|
||||||
title={showCoordinates ? 'Координаты: вкл' : 'Координаты: выкл'}
|
|
||||||
aria-label='Переключатель видимости координат (для отладки)'
|
|
||||||
icon={<IconCoordinates size='1.25rem' className={showCoordinates ? 'icon-green' : 'icon-primary'} />}
|
|
||||||
onClick={toggleShowCoordinates}
|
|
||||||
/>
|
/>
|
||||||
<BadgeHelp topic={HelpTopic.UI_OSS_GRAPH} contentClass='sm:max-w-160' offset={4} />
|
<BadgeHelp topic={HelpTopic.UI_OSS_GRAPH} contentClass='sm:max-w-160' offset={4} />
|
||||||
</div>
|
</div>
|
||||||
|
|
24
rsconcept/frontend/src/hooks/use-throttle-callback.ts
Normal file
24
rsconcept/frontend/src/hooks/use-throttle-callback.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
/** Throttles a callback to only run once per delay. */
|
||||||
|
export function useThrottleCallback<Callback extends (...args: never[]) => void>(
|
||||||
|
callback: Callback,
|
||||||
|
delay: number
|
||||||
|
): Callback {
|
||||||
|
const lastCalled = useRef(0);
|
||||||
|
|
||||||
|
const throttled = useCallback(
|
||||||
|
(...args: Parameters<Callback>) => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastCalled.current >= delay) {
|
||||||
|
lastCalled.current = now;
|
||||||
|
callback(...args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[callback, delay]
|
||||||
|
);
|
||||||
|
|
||||||
|
return throttled as Callback;
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ export const DialogType = {
|
||||||
DELETE_OPERATION: 10,
|
DELETE_OPERATION: 10,
|
||||||
CHANGE_INPUT_SCHEMA: 11,
|
CHANGE_INPUT_SCHEMA: 11,
|
||||||
RELOCATE_CONSTITUENTS: 12,
|
RELOCATE_CONSTITUENTS: 12,
|
||||||
|
OSS_SETTINGS: 26,
|
||||||
|
|
||||||
CLONE_LIBRARY_ITEM: 13,
|
CLONE_LIBRARY_ITEM: 13,
|
||||||
UPLOAD_RSFORM: 14,
|
UPLOAD_RSFORM: 14,
|
||||||
|
@ -91,6 +92,7 @@ interface DialogsStore {
|
||||||
showCreateVersion: (props: DlgCreateVersionProps) => void;
|
showCreateVersion: (props: DlgCreateVersionProps) => void;
|
||||||
showDeleteOperation: (props: DlgDeleteOperationProps) => void;
|
showDeleteOperation: (props: DlgDeleteOperationProps) => void;
|
||||||
showGraphParams: () => void;
|
showGraphParams: () => void;
|
||||||
|
showOssOptions: () => void;
|
||||||
showRelocateConstituents: (props: DlgRelocateConstituentsProps) => void;
|
showRelocateConstituents: (props: DlgRelocateConstituentsProps) => void;
|
||||||
showRenameCst: (props: DlgRenameCstProps) => void;
|
showRenameCst: (props: DlgRenameCstProps) => void;
|
||||||
showQR: (props: DlgShowQRProps) => void;
|
showQR: (props: DlgShowQRProps) => void;
|
||||||
|
@ -128,6 +130,7 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
|
||||||
showCreateVersion: props => set({ active: DialogType.CREATE_VERSION, props: props }),
|
showCreateVersion: props => set({ active: DialogType.CREATE_VERSION, props: props }),
|
||||||
showDeleteOperation: props => set({ active: DialogType.DELETE_OPERATION, props: props }),
|
showDeleteOperation: props => set({ active: DialogType.DELETE_OPERATION, props: props }),
|
||||||
showGraphParams: () => set({ active: DialogType.GRAPH_PARAMETERS, props: null }),
|
showGraphParams: () => set({ active: DialogType.GRAPH_PARAMETERS, props: null }),
|
||||||
|
showOssOptions: () => set({ active: DialogType.OSS_SETTINGS, props: null }),
|
||||||
showRelocateConstituents: props => set({ active: DialogType.RELOCATE_CONSTITUENTS, props: props }),
|
showRelocateConstituents: props => set({ active: DialogType.RELOCATE_CONSTITUENTS, props: props }),
|
||||||
showRenameCst: props => set({ active: DialogType.RENAME_CONSTITUENTA, props: props }),
|
showRenameCst: props => set({ active: DialogType.RENAME_CONSTITUENTA, props: props }),
|
||||||
showQR: props => set({ active: DialogType.SHOW_QR_CODE, props: props }),
|
showQR: props => set({ active: DialogType.SHOW_QR_CODE, props: props }),
|
||||||
|
|
|
@ -325,6 +325,7 @@
|
||||||
.selected & {
|
.selected & {
|
||||||
color: var(--color-foreground);
|
color: var(--color-foreground);
|
||||||
border-color: var(--color-graph-selected);
|
border-color: var(--color-graph-selected);
|
||||||
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user