diff --git a/rsconcept/frontend/src/app/global-dialogs.tsx b/rsconcept/frontend/src/app/global-dialogs.tsx index f0c79d34..911833a5 100644 --- a/rsconcept/frontend/src/app/global-dialogs.tsx +++ b/rsconcept/frontend/src/app/global-dialogs.tsx @@ -123,6 +123,11 @@ const DlgEditBlock = React.lazy(() => default: module.DlgEditBlock })) ); +const DlgOssSettings = React.lazy(() => + import('@/features/oss/dialogs/dlg-oss-settings').then(module => ({ + default: module.DlgOssSettings + })) +); export const GlobalDialogs = () => { const active = useDialogsStore(state => state.active); @@ -155,6 +160,8 @@ export const GlobalDialogs = () => { return ; case DialogType.INLINE_SYNTHESIS: return ; + case DialogType.OSS_SETTINGS: + return ; case DialogType.SHOW_AST: return ; case DialogType.SHOW_TYPE_GRAPH: diff --git a/rsconcept/frontend/src/components/icons.tsx b/rsconcept/frontend/src/components/icons.tsx index c833233d..93468bcc 100644 --- a/rsconcept/frontend/src/components/icons.tsx +++ b/rsconcept/frontend/src/components/icons.tsx @@ -16,7 +16,7 @@ export { FiEdit as IconEdit2 } from 'react-icons/fi'; export { BiSearchAlt2 as IconSearch } from 'react-icons/bi'; export { BiDownload as IconDownload } 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 { TbEyeX as IconHide } from 'react-icons/tb'; 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'; // ======== Graph UI ======= +export { LuLayoutDashboard as IconFixLayout } from 'react-icons/lu'; export { BiCollapse as IconGraphCollapse } from 'react-icons/bi'; export { BiExpand as IconGraphExpand } from 'react-icons/bi'; export { LuMaximize as IconGraphMaximize } from 'react-icons/lu'; diff --git a/rsconcept/frontend/src/components/input/checkbox.tsx b/rsconcept/frontend/src/components/input/checkbox.tsx index 85f223c8..ef699d68 100644 --- a/rsconcept/frontend/src/components/input/checkbox.tsx +++ b/rsconcept/frontend/src/components/input/checkbox.tsx @@ -14,10 +14,13 @@ export interface CheckboxProps extends Omit void; + + /** Custom icon to display next instead of checkbox. */ + customIcon?: (checked?: boolean) => React.ReactNode; } /** @@ -31,6 +34,7 @@ export function Checkbox({ hideTitle, className, value, + customIcon, onChange, ...restProps }: CheckboxProps) { @@ -63,15 +67,19 @@ export function Checkbox({ disabled={disabled} {...restProps} > -
- {value ? : null} -
+ {customIcon ? ( + customIcon(value) + ) : ( +
+ {value ? : null} +
+ )} {label ? {label} : null} ); diff --git a/rsconcept/frontend/src/features/oss/dialogs/dlg-oss-settings.tsx b/rsconcept/frontend/src/features/oss/dialogs/dlg-oss-settings.tsx new file mode 100644 index 00000000..812d361e --- /dev/null +++ b/rsconcept/frontend/src/features/oss/dialogs/dlg-oss-settings.tsx @@ -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 ( + + } + /> + } + /> + + checked ? ( + + ) : ( + + ) + } + /> + + checked ? ( + + ) : ( + + ) + } + /> + + ); +} diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx index b59e4aa0..71636ee4 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx @@ -46,7 +46,7 @@ export function BlockNode(node: BlockInternalNode) { 'cc-node-block h-full w-full', isDragging && isParent && dropTarget !== node.data.block.id && 'border-destructive', ((isParent && !isDragging) || dropTarget === node.data.block.id) && 'border-primary', - isChild && 'border-accent-orange50' + isChild && 'border-accent-orange' )} >
id < 0); + blocks = blocks.filter(block => !successors.includes(-block.id)); + if (blocks.length === 0) { + return null; + } if (blocks.length === 1) { return blocks[0].id; } @@ -317,13 +326,13 @@ export function OssFlow() { setIsContextMenuOpen(false); } - function handleDrag(event: React.MouseEvent) { + const handleDrag = useThrottleCallback((event: React.MouseEvent) => { if (containMovement) { return; } setIsDragging(true); setDropTarget(determineDropTarget(event)); - } + }, DRAG_THROTTLE_DELAY); function handleDragStop(event: React.MouseEvent, target: Node) { if (containMovement) { diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx index 4171d45a..a5de6d46 100644 --- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx +++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/toolbar-oss-graph.tsx @@ -1,5 +1,6 @@ 'use client'; +import { toast } from 'react-toastify'; import { useReactFlow } from 'reactflow'; import { HelpTopic } from '@/features/help'; @@ -9,20 +10,16 @@ import { useUpdateLayout } from '@/features/oss/backend/use-update-layout'; import { MiniButton } from '@/components/control'; import { - IconAnimation, - IconAnimationOff, IconConceptBlock, - IconCoordinates, IconDestroy, IconEdit2, IconExecute, IconFitImage, - IconGrid, - IconLineStraight, - IconLineWave, + IconFixLayout, IconNewItem, IconReset, - IconSave + IconSave, + IconSettings } from '@/components/icons'; import { type Styling } from '@/components/props'; import { cn } from '@/components/utils'; @@ -32,7 +29,6 @@ import { prepareTooltip } from '@/utils/utils'; import { OperationType } from '../../../backend/types'; import { useMutatingOss } from '../../../backend/use-mutating-oss'; -import { useOSSGraphStore } from '../../../stores/oss-graph'; import { useOssEdit } from '../oss-edit-context'; 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 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 { 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) { @@ -100,6 +88,15 @@ export function ToolbarOssGraph({ fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }); } + function handleFixLayout() { + // TODO: implement layout algorithm + toast.info('Еще не реализовано'); + } + + function handleShowOptions() { + showOssOptions(); + } + function handleSavePositions() { void updateLayout({ itemID: schema.id, data: getLayout() }); } @@ -152,46 +149,15 @@ export function ToolbarOssGraph({ onClick={handleFitView} /> - ) : ( - - ) - } - onClick={toggleShowGrid} + title='Исправить позиции узлов' + icon={} + onClick={handleFixLayout} + disabled={selected.length > 1 || selected[0] > 0} /> - ) : ( - - ) - } - onClick={toggleEdgeStraight} - /> - - ) : ( - - ) - } - onClick={toggleEdgeAnimate} - /> - } - onClick={toggleShowCoordinates} + title='Настройки отображения' + icon={} + onClick={handleShowOptions} />
diff --git a/rsconcept/frontend/src/hooks/use-throttle-callback.ts b/rsconcept/frontend/src/hooks/use-throttle-callback.ts new file mode 100644 index 00000000..c3bf450f --- /dev/null +++ b/rsconcept/frontend/src/hooks/use-throttle-callback.ts @@ -0,0 +1,24 @@ +'use client'; + +import { useCallback, useRef } from 'react'; + +/** Throttles a callback to only run once per delay. */ +export function useThrottleCallback void>( + callback: Callback, + delay: number +): Callback { + const lastCalled = useRef(0); + + const throttled = useCallback( + (...args: Parameters) => { + const now = Date.now(); + if (now - lastCalled.current >= delay) { + lastCalled.current = now; + callback(...args); + } + }, + [callback, delay] + ); + + return throttled as Callback; +} diff --git a/rsconcept/frontend/src/stores/dialogs.ts b/rsconcept/frontend/src/stores/dialogs.ts index 801330a1..87d1ed7e 100644 --- a/rsconcept/frontend/src/stores/dialogs.ts +++ b/rsconcept/frontend/src/stores/dialogs.ts @@ -44,6 +44,7 @@ export const DialogType = { DELETE_OPERATION: 10, CHANGE_INPUT_SCHEMA: 11, RELOCATE_CONSTITUENTS: 12, + OSS_SETTINGS: 26, CLONE_LIBRARY_ITEM: 13, UPLOAD_RSFORM: 14, @@ -91,6 +92,7 @@ interface DialogsStore { showCreateVersion: (props: DlgCreateVersionProps) => void; showDeleteOperation: (props: DlgDeleteOperationProps) => void; showGraphParams: () => void; + showOssOptions: () => void; showRelocateConstituents: (props: DlgRelocateConstituentsProps) => void; showRenameCst: (props: DlgRenameCstProps) => void; showQR: (props: DlgShowQRProps) => void; @@ -128,6 +130,7 @@ export const useDialogsStore = create()(set => ({ showCreateVersion: props => set({ active: DialogType.CREATE_VERSION, props: props }), showDeleteOperation: props => set({ active: DialogType.DELETE_OPERATION, props: props }), 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 }), showRenameCst: props => set({ active: DialogType.RENAME_CONSTITUENTA, props: props }), showQR: props => set({ active: DialogType.SHOW_QR_CODE, props: props }), diff --git a/rsconcept/frontend/src/styling/components.css b/rsconcept/frontend/src/styling/components.css index 44d3c01c..4789c330 100644 --- a/rsconcept/frontend/src/styling/components.css +++ b/rsconcept/frontend/src/styling/components.css @@ -325,6 +325,7 @@ .selected & { color: var(--color-foreground); border-color: var(--color-graph-selected); + border-style: solid; } &:hover {