Compare commits

...

6 Commits

Author SHA1 Message Date
psychedelicious
70ac58e64a tidy(ui): remove unused props 2025-07-25 18:51:21 +10:00
psychedelicious
e653837236 fix(ui): add separate wrapper components for notes and current image nodes that do not need invocation node context 2025-07-25 18:51:21 +10:00
psychedelicious
2bbfcc2f13 fix(ui): ensure all node context provider wraps all calls to useInvocationNodeContext 2025-07-25 18:51:21 +10:00
psychedelicious
d6e0e439c5 perf(ui): imperatively get nodes and edges in autolayout hook 2025-07-25 18:50:59 +10:00
psychedelicious
26aab60f81 chore: bump version to v6.2.0 2025-07-25 18:41:00 +10:00
Riccardo Giovanetti
7bea2fa11f translationBot(ui): update translation (Italian)
Currently translated at 98.6% (2016 of 2044 strings)

translationBot(ui): update translation (Italian)

Currently translated at 98.6% (2015 of 2043 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2025-07-25 17:15:01 +10:00
10 changed files with 308 additions and 123 deletions

View File

@@ -254,8 +254,8 @@
"desc": "Attiva/disattiva il pannello destro."
},
"resetPanelLayout": {
"title": "Ripristina il layout del pannello",
"desc": "Ripristina le dimensioni e il layout predefiniti dei pannelli sinistro e destro."
"title": "Ripristina lo schema del pannello",
"desc": "Ripristina le dimensioni e lo schema predefiniti dei pannelli sinistro e destro."
},
"togglePanels": {
"title": "Attiva/disattiva i pannelli",
@@ -539,6 +539,10 @@
"galleryNavUpAlt": {
"desc": "Uguale a Naviga verso l'alto, ma seleziona l'immagine da confrontare, aprendo la modalità di confronto se non è già aperta.",
"title": "Naviga verso l'alto (Confronta immagine)"
},
"starImage": {
"desc": "Aggiungi/Rimuovi contrassegno all'immagine selezionata.",
"title": "Aggiungi / Rimuovi contrassegno immagine"
}
}
},
@@ -1162,7 +1166,19 @@
"unexpectedField_withName": "Campo \"{{name}}\" inaspettato",
"missingSourceOrTargetHandle": "Identificatore del nodo sorgente o di destinazione mancante",
"layout": {
"alignmentDR": "In basso a destra"
"alignmentDR": "In basso a destra",
"autoLayout": "Schema automatico",
"nodeSpacing": "Spaziatura nodi",
"layerSpacing": "Spaziatura livelli",
"layeringStrategy": "Strategia livelli",
"longestPath": "Percorso più lungo",
"layoutDirection": "Direzione schema",
"layoutDirectionRight": "Orizzontale",
"layoutDirectionDown": "Verticale",
"alignment": "Allineamento nodi",
"alignmentUL": "In alto a sinistra",
"alignmentDL": "In basso a sinistra",
"alignmentUR": "In alto a destra"
}
},
"boards": {
@@ -1240,7 +1256,7 @@
"batchQueuedDesc_other": "Aggiunte {{count}} sessioni a {{direction}} della coda",
"graphQueued": "Grafico in coda",
"batch": "Lotto",
"clearQueueAlertDialog": "Lo svuotamento della coda annulla immediatamente tutti gli elementi in elaborazione e cancella completamente la coda. I filtri in sospeso verranno annullati.",
"clearQueueAlertDialog": "La cancellazione della coda annulla immediatamente tutti gli elementi in elaborazione e cancella completamente la coda. I filtri in sospeso verranno annullati e l'area di lavoro della Tela verrà reimpostata.",
"pending": "In attesa",
"completedIn": "Completato in",
"resumeFailed": "Problema nel riavvio dell'elaborazione",
@@ -1296,7 +1312,8 @@
"retrySucceeded": "Elemento rieseguito",
"retryItem": "Riesegui elemento",
"retryFailed": "Problema riesecuzione elemento",
"credits": "Crediti"
"credits": "Crediti",
"cancelAllExceptCurrent": "Annulla tutto tranne quello corrente"
},
"models": {
"noMatchingModels": "Nessun modello corrispondente",
@@ -1711,7 +1728,7 @@
"structure": {
"heading": "Struttura",
"paragraphs": [
"La struttura determina quanto l'immagine finale rispecchierà il layout dell'originale. Una struttura bassa permette cambiamenti significativi, mentre una struttura alta conserva la composizione e il layout originali."
"La struttura determina quanto l'immagine finale rispecchierà il layout dell'originale. Un valore struttura basso permette cambiamenti significativi, mentre un valore struttura alto conserva la composizione e lo schema originali."
]
},
"fluxDevLicense": {
@@ -1877,7 +1894,7 @@
"opened": "Aperto",
"convertGraph": "Converti grafico",
"loadWorkflow": "$t(common.load) Flusso di lavoro",
"autoLayout": "Disposizione automatica",
"autoLayout": "Schema automatico",
"loadFromGraph": "Carica il flusso di lavoro dal grafico",
"userWorkflows": "Flussi di lavoro utente",
"projectWorkflows": "Flussi di lavoro del progetto",
@@ -2631,9 +2648,10 @@
"watchRecentReleaseVideos": "Guarda i video su questa versione",
"watchUiUpdatesOverview": "Guarda le novità dell'interfaccia",
"items": [
"Genera immagini più velocemente con le nuove Rampe di lancio e una scheda Genera semplificata.",
"Modifica con prompt utilizzando Flux Kontext Dev.",
"Esporta in PSD, nascondi sovrapposizioni in blocco, organizza modelli e immagini: il tutto in un'interfaccia riprogettata e pensata per il controllo."
"Nuova impostazione per inviare tutte le generazioni della Tela direttamente alla Galleria.",
"Nuove funzionalità Inverti maschera (Maiusc+V) e Adatta il Riquadro di delimitazione alla maschera (Maiusc+B).",
"Supporto esteso per miniature e configurazioni dei modelli.",
"Vari altri aggiornamenti e correzioni per la qualità della vita"
]
},
"system": {

View File

@@ -6,7 +6,7 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { DndImage } from 'features/dnd/DndImage';
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import NonInvocationNodeWrapper from 'features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import type { AnimationProps } from 'framer-motion';
import { motion } from 'framer-motion';
@@ -58,13 +58,14 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
}, []);
const { t } = useTranslation();
return (
<NodeWrapper nodeId={props.nodeProps.id} selected={props.nodeProps.selected} width={384}>
<NonInvocationNodeWrapper nodeId={props.nodeProps.id} selected={props.nodeProps.selected} width={384}>
<Flex
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={DRAG_HANDLE_CLASSNAME}
position="relative"
flexDirection="column"
aspectRatio="1/1"
>
<Flex layerStyle="nodeHeader" borderTopRadius="base" alignItems="center" justifyContent="center" h={8}>
<Text fontSize="sm" fontWeight="semibold" color="base.200">
@@ -80,7 +81,7 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
)}
</Flex>
</Flex>
</NodeWrapper>
</NonInvocationNodeWrapper>
);
};

View File

@@ -3,8 +3,8 @@ import { createSelector } from '@reduxjs/toolkit';
import type { Node, NodeProps } from '@xyflow/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import NonInvocationNodeTitle from 'features/nodes/components/flow/nodes/common/NonInvocationNodeTitle';
import NonInvocationNodeWrapper from 'features/nodes/components/flow/nodes/common/NonInvocationNodeWrapper';
import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import { NO_DRAG_CLASS, NO_PAN_CLASS } from 'features/nodes/types/constants';
@@ -34,7 +34,7 @@ const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
}
return (
<NodeWrapper nodeId={nodeId} selected={selected}>
<NonInvocationNodeWrapper nodeId={nodeId} selected={selected}>
<Flex
layerStyle="nodeHeader"
borderTopRadius="base"
@@ -44,7 +44,7 @@ const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
h={8}
>
<NodeCollapseButton nodeId={nodeId} isOpen={isOpen} />
<NodeTitle nodeId={nodeId} title="Notes" />
<NonInvocationNodeTitle nodeId={nodeId} title="Notes" />
<Box minW={8} />
</Flex>
{isOpen && (
@@ -73,7 +73,7 @@ const NotesNode = (props: NodeProps<Node<NotesNodeData>>) => {
</Flex>
</>
)}
</NodeWrapper>
</NonInvocationNodeWrapper>
);
};

View File

@@ -1,4 +1,4 @@
import type { ChakraProps, SystemStyleObject } from '@invoke-ai/ui-library';
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useInvocationNodeContext } from 'features/nodes/components/flow/nodes/Invocation/context';
@@ -12,6 +12,8 @@ import { zNodeStatus } from 'features/nodes/types/invocation';
import type { MouseEvent, PropsWithChildren } from 'react';
import { memo, useCallback } from 'react';
import { containerSx, inProgressSx, shadowsSx } from './shared';
type NodeWrapperProps = PropsWithChildren & {
nodeId: string;
selected: boolean;
@@ -19,100 +21,6 @@ type NodeWrapperProps = PropsWithChildren & {
isMissingTemplate?: boolean;
};
// Certain CSS transitions are disabled as a performance optimization - they can cause massive slowdowns in large
// workflows even when the animations are GPU-accelerated CSS.
const containerSx: SystemStyleObject = {
h: 'full',
position: 'relative',
borderRadius: 'base',
transitionProperty: 'none',
cursor: 'grab',
'--border-color': 'var(--invoke-colors-base-500)',
'--border-color-selected': 'var(--invoke-colors-blue-300)',
'--header-bg-color': 'var(--invoke-colors-base-900)',
'&[data-status="warning"]': {
'--border-color': 'var(--invoke-colors-warning-500)',
'--border-color-selected': 'var(--invoke-colors-warning-500)',
'--header-bg-color': 'var(--invoke-colors-warning-700)',
},
'&[data-status="error"]': {
'--border-color': 'var(--invoke-colors-error-500)',
'--border-color-selected': 'var(--invoke-colors-error-500)',
'--header-bg-color': 'var(--invoke-colors-error-700)',
},
// The action buttons are hidden by default and shown on hover
'& .node-selection-overlay': {
display: 'block',
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
transitionProperty: 'none',
pointerEvents: 'none',
shadow: '0 0 0 1px var(--border-color)',
},
'&[data-is-mouse-over-node="true"] .node-selection-overlay': {
display: 'block',
},
'&[data-is-mouse-over-form-field="true"] .node-selection-overlay': {
display: 'block',
bg: 'invokeBlueAlpha.100',
},
_hover: {
'& .node-selection-overlay': {
display: 'block',
shadow: '0 0 0 1px var(--border-color-selected)',
},
'&[data-is-selected="true"] .node-selection-overlay': {
display: 'block',
shadow: '0 0 0 2px var(--border-color-selected)',
},
},
'&[data-is-selected="true"] .node-selection-overlay': {
display: 'block',
shadow: '0 0 0 2px var(--border-color-selected)',
},
'&[data-is-editor-locked="true"]': {
'& *': {
cursor: 'not-allowed',
pointerEvents: 'none',
},
},
};
const shadowsSx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
pointerEvents: 'none',
zIndex: -1,
shadow: 'var(--invoke-shadows-xl), var(--invoke-shadows-base), var(--invoke-shadows-base)',
};
const inProgressSx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'md',
pointerEvents: 'none',
transitionProperty: 'none',
opacity: 0.7,
zIndex: -1,
display: 'none',
shadow: '0 0 0 2px var(--invoke-colors-yellow-400), 0 0 20px 2px var(--invoke-colors-orange-700)',
'&[data-is-in-progress="true"]': {
display: 'block',
},
};
const NodeWrapper = (props: NodeWrapperProps) => {
const { nodeId, width, children, isMissingTemplate, selected } = props;
const ctx = useInvocationNodeContext();

View File

@@ -0,0 +1,69 @@
import { Flex, Input, Text } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useEditable } from 'common/hooks/useEditable';
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
import { NO_FIT_ON_DOUBLE_CLICK_CLASS } from 'features/nodes/types/constants';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
nodeId: string;
title: string;
};
const NonInvocationNodeTitle = ({ nodeId, title }: Props) => {
const dispatch = useAppDispatch();
const selectNodeLabel = useMemo(
() =>
createSelector(selectNodes, (nodes) => {
const node = nodes.find((n) => n.id === nodeId);
return node?.data?.label ?? '';
}),
[nodeId]
);
const label = useAppSelector(selectNodeLabel);
const { t } = useTranslation();
const inputRef = useRef<HTMLInputElement>(null);
const onChange = useCallback(
(label: string) => {
dispatch(nodeLabelChanged({ nodeId, label }));
},
[dispatch, nodeId]
);
const editable = useEditable({
value: label || title || t('nodes.problemSettingTitle'),
defaultValue: title || t('nodes.problemSettingTitle'),
onChange,
inputRef,
});
return (
<Flex overflow="hidden" w="full" h="full" alignItems="center" justifyContent="center">
{!editable.isEditing && (
<Text
className={NO_FIT_ON_DOUBLE_CLICK_CLASS}
fontWeight="semibold"
color="base.200"
onDoubleClick={editable.startEditing}
noOfLines={1}
>
{editable.value}
</Text>
)}
{editable.isEditing && (
<Input
ref={inputRef}
{...editable.inputProps}
variant="outline"
_focusVisible={{ borderRadius: 'base', h: 'unset' }}
/>
)}
</Flex>
);
};
export default memo(NonInvocationNodeTitle);

View File

@@ -0,0 +1,80 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
import { selectNodeOpacity } from 'features/nodes/store/workflowSettingsSlice';
import { DRAG_HANDLE_CLASSNAME, NO_FIT_ON_DOUBLE_CLICK_CLASS, NODE_WIDTH } from 'features/nodes/types/constants';
import { zNodeStatus } from 'features/nodes/types/invocation';
import type { MouseEvent, PropsWithChildren } from 'react';
import { memo, useCallback } from 'react';
import { containerSx, inProgressSx, shadowsSx } from './shared';
type NonInvocationNodeWrapperProps = PropsWithChildren & {
nodeId: string;
selected: boolean;
width?: ChakraProps['w'];
};
const NonInvocationNodeWrapper = (props: NonInvocationNodeWrapperProps) => {
const { nodeId, width, children, selected } = props;
const mouseOverNode = useMouseOverNode(nodeId);
const zoomToNode = useZoomToNode(nodeId);
const isLocked = useIsWorkflowEditorLocked();
const executionState = useNodeExecutionState(nodeId);
const isInProgress = executionState?.status === zNodeStatus.enum.IN_PROGRESS;
const opacity = useAppSelector(selectNodeOpacity);
const globalMenu = useGlobalMenuClose();
const onDoubleClick = useCallback(
(e: MouseEvent) => {
if (!(e.target instanceof HTMLElement)) {
// We have to manually narrow the type here thanks to a TS quirk
return;
}
if (
e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLSelectElement ||
e.target instanceof HTMLButtonElement ||
e.target instanceof HTMLAnchorElement
) {
// Don't fit the view if the user is editing a text field, select, button, or link
return;
}
if (e.target.closest(`.${NO_FIT_ON_DOUBLE_CLICK_CLASS}`) !== null) {
// This target is marked as not fitting the view on double click
return;
}
zoomToNode();
},
[zoomToNode]
);
return (
<Box
onClick={globalMenu.onCloseGlobal}
onDoubleClick={onDoubleClick}
onMouseOver={mouseOverNode.handleMouseOver}
onMouseOut={mouseOverNode.handleMouseOut}
className={DRAG_HANDLE_CLASSNAME}
sx={containerSx}
width={width || NODE_WIDTH}
opacity={opacity}
data-is-editor-locked={isLocked}
data-is-selected={selected}
>
<Box sx={shadowsSx} />
<Box sx={inProgressSx} data-is-in-progress={isInProgress} />
{children}
<Box className="node-selection-overlay" />
</Box>
);
};
export default memo(NonInvocationNodeWrapper);

View File

@@ -0,0 +1,95 @@
// Certain CSS transitions are disabled as a performance optimization - they can cause massive slowdowns in large
// workflows even when the animations are GPU-accelerated CSS.
import type { SystemStyleObject } from '@invoke-ai/ui-library';
export const containerSx: SystemStyleObject = {
h: 'full',
position: 'relative',
borderRadius: 'base',
transitionProperty: 'none',
cursor: 'grab',
'--border-color': 'var(--invoke-colors-base-500)',
'--border-color-selected': 'var(--invoke-colors-blue-300)',
'--header-bg-color': 'var(--invoke-colors-base-900)',
'&[data-status="warning"]': {
'--border-color': 'var(--invoke-colors-warning-500)',
'--border-color-selected': 'var(--invoke-colors-warning-500)',
'--header-bg-color': 'var(--invoke-colors-warning-700)',
},
'&[data-status="error"]': {
'--border-color': 'var(--invoke-colors-error-500)',
'--border-color-selected': 'var(--invoke-colors-error-500)',
'--header-bg-color': 'var(--invoke-colors-error-700)',
},
// The action buttons are hidden by default and shown on hover
'& .node-selection-overlay': {
display: 'block',
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
transitionProperty: 'none',
pointerEvents: 'none',
shadow: '0 0 0 1px var(--border-color)',
},
'&[data-is-mouse-over-node="true"] .node-selection-overlay': {
display: 'block',
},
'&[data-is-mouse-over-form-field="true"] .node-selection-overlay': {
display: 'block',
bg: 'invokeBlueAlpha.100',
},
_hover: {
'& .node-selection-overlay': {
display: 'block',
shadow: '0 0 0 1px var(--border-color-selected)',
},
'&[data-is-selected="true"] .node-selection-overlay': {
display: 'block',
shadow: '0 0 0 2px var(--border-color-selected)',
},
},
'&[data-is-selected="true"] .node-selection-overlay': {
display: 'block',
shadow: '0 0 0 2px var(--border-color-selected)',
},
'&[data-is-editor-locked="true"]': {
'& *': {
cursor: 'not-allowed',
pointerEvents: 'none',
},
},
};
export const shadowsSx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'base',
pointerEvents: 'none',
zIndex: -1,
shadow: 'var(--invoke-shadows-xl), var(--invoke-shadows-base), var(--invoke-shadows-base)',
};
export const inProgressSx: SystemStyleObject = {
position: 'absolute',
top: 0,
insetInlineEnd: 0,
bottom: 0,
insetInlineStart: 0,
borderRadius: 'md',
pointerEvents: 'none',
transitionProperty: 'none',
opacity: 0.7,
zIndex: -1,
display: 'none',
shadow: '0 0 0 2px var(--invoke-colors-yellow-400), 0 0 20px 2px var(--invoke-colors-orange-700)',
'&[data-is-in-progress="true"]': {
display: 'block',
},
};

View File

@@ -18,6 +18,7 @@ import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableCon
import { withResultAsync } from 'common/util/result';
import { parseify } from 'common/util/serialize';
import { ExternalLink } from 'features/gallery/components/ImageViewer/NoContentForViewer';
import { InvocationNodeContextProvider } from 'features/nodes/components/flow/nodes/Invocation/context';
import { NodeFieldElementOverlay } from 'features/nodes/components/sidePanel/builder/NodeFieldElementEditMode';
import { useDoesWorkflowHaveUnsavedChanges } from 'features/nodes/components/sidePanel/workflow/IsolatedWorkflowBuilderWatcher';
import {
@@ -89,7 +90,11 @@ const OutputFields = memo(() => {
{t('workflows.builder.noOutputNodeSelected')}
</Text>
)}
{outputNodeId && <OutputFieldsContent outputNodeId={outputNodeId} />}
{outputNodeId && (
<InvocationNodeContextProvider nodeId={outputNodeId}>
<OutputFieldsContent outputNodeId={outputNodeId} />
</InvocationNodeContextProvider>
)}
</Flex>
);
});
@@ -127,7 +132,11 @@ const PublishableInputFields = memo(() => {
<Text fontWeight="semibold">{t('workflows.builder.publishedWorkflowInputs')}</Text>
<Divider />
{inputs.publishable.map(({ nodeId, fieldName }) => {
return <NodeInputFieldPreview key={`${nodeId}-${fieldName}`} nodeId={nodeId} fieldName={fieldName} />;
return (
<InvocationNodeContextProvider nodeId={nodeId} key={`${nodeId}-${fieldName}`}>
<NodeInputFieldPreview nodeId={nodeId} fieldName={fieldName} />
</InvocationNodeContextProvider>
);
})}
</Flex>
);
@@ -149,7 +158,11 @@ const UnpublishableInputFields = memo(() => {
</Text>
<Divider />
{inputs.unpublishable.map(({ nodeId, fieldName }) => {
return <NodeInputFieldPreview key={`${nodeId}-${fieldName}`} nodeId={nodeId} fieldName={fieldName} />;
return (
<InvocationNodeContextProvider nodeId={nodeId} key={`${nodeId}-${fieldName}`}>
<NodeInputFieldPreview key={`${nodeId}-${fieldName}`} nodeId={nodeId} fieldName={fieldName} />
</InvocationNodeContextProvider>
);
})}
</Flex>
);

View File

@@ -1,6 +1,6 @@
import { graphlib, layout } from '@dagrejs/dagre';
import type { Edge, NodePositionChange } from '@xyflow/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { nodesChanged } from 'features/nodes/store/nodesSlice';
import { selectEdges, selectNodes } from 'features/nodes/store/selectors';
import {
@@ -36,9 +36,7 @@ const getNodeWidth = (node: AnyNode): number => {
};
export const useAutoLayout = (): (() => void) => {
const dispatch = useAppDispatch();
const nodes = useAppSelector(selectNodes);
const edges = useAppSelector(selectEdges);
const store = useAppStore();
const nodeSpacing = useAppSelector(selectNodeSpacing);
const layerSpacing = useAppSelector(selectLayerSpacing);
const layeringStrategy = useAppSelector(selectLayeringStrategy);
@@ -46,6 +44,9 @@ export const useAutoLayout = (): (() => void) => {
const nodeAlignment = useAppSelector(selectNodeAlignment);
const autoLayout = useCallback(() => {
const state = store.getState();
const nodes = selectNodes(state);
const edges = selectEdges(state);
// We'll do graph layout using dagre, then convert the results to reactflow position changes
const g = new graphlib.Graph();
@@ -131,8 +132,8 @@ export const useAutoLayout = (): (() => void) => {
return { id: node.id, type: 'position', position: newPosition };
});
dispatch(nodesChanged(positionChanges));
}, [dispatch, edges, nodes, nodeSpacing, layerSpacing, layeringStrategy, layoutDirection, nodeAlignment]);
store.dispatch(nodesChanged(positionChanges));
}, [layerSpacing, layeringStrategy, layoutDirection, nodeAlignment, nodeSpacing, store]);
return autoLayout;
};

View File

@@ -1 +1 @@
__version__ = "6.1.0"
__version__ = "6.2.0"