From 3651cf7ee24608d5937d72fb55e583c481dcb49f Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 23 Aug 2023 16:24:09 +1000 Subject: [PATCH] wip buttons --- invokeai/frontend/web/public/locales/en.json | 9 ++- .../src/common/hooks/useIsReadyToInvoke.ts | 4 + .../TopCenterPanel/NodeInvokeButton.tsx | 81 ------------------- .../TopCenterPanel/ReloadSchemaButton.tsx | 10 ++- .../panels/TopCenterPanel/TopCenterPanel.tsx | 20 ++--- .../TopCenterPanel/WorkflowEditorControls.tsx | 18 +++++ .../panels/TopRightPanel/TopRightPanel.tsx | 2 + .../WorkflowEditorSettings.tsx} | 19 +++-- .../nodes/hooks/useLoadWorkflowFromFile.tsx | 6 +- .../ProcessButtons/InvokeButton.tsx | 48 ++++------- .../components/InvokeAILogoComponent.tsx | 2 +- .../src/features/system/store/systemSlice.ts | 6 +- .../web/src/theme/components/button.ts | 24 +++++- .../web/src/theme/components/progress.ts | 2 +- 14 files changed, 96 insertions(+), 155 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeInvokeButton.tsx create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/WorkflowEditorControls.tsx rename invokeai/frontend/web/src/features/nodes/components/flow/panels/{TopCenterPanel/NodeEditorSettings.tsx => TopRightPanel/WorkflowEditorSettings.tsx} (93%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 4d4246d8c2..f4bfa282fa 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -718,10 +718,11 @@ "swapSizes": "Swap Sizes" }, "nodes": { - "reloadSchema": "Reload Schema", - "saveGraph": "Save Graph", - "loadGraph": "Load Graph (saved from Node Editor) (Do not copy-paste metadata)", - "clearGraph": "Clear Graph", + "reloadSchema": "Reload Node Templates", + "saveGraph": "Save Workflow", + "loadGraph": "Load Workflow", + "resetGraph": "Reset Workflow", + "clearGraph": "Reset Graph", "clearGraphDesc": "Are you sure you want to clear all nodes?", "zoomInNodes": "Zoom In", "zoomOutNodes": "Zoom Out", diff --git a/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts index ac770e3787..e06a1106c1 100644 --- a/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts +++ b/invokeai/frontend/web/src/common/hooks/useIsReadyToInvoke.ts @@ -32,6 +32,10 @@ const selector = createSelector( } if (activeTabName === 'nodes' && nodes.shouldValidateGraph) { + if (!nodes.nodes.length) { + reasons.push('No nodes in graph'); + } + nodes.nodes.forEach((node) => { if (!isInvocationNode(node)) { return; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeInvokeButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeInvokeButton.tsx deleted file mode 100644 index decaea19e8..0000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeInvokeButton.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Box } from '@chakra-ui/react'; -import { userInvoked } from 'app/store/actions'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import IAIButton, { IAIButtonProps } from 'common/components/IAIButton'; -import { IAIIconButtonProps } from 'common/components/IAIIconButton'; -import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; -import { InvokeButtonTooltipContent } from 'features/parameters/components/ProcessButtons/InvokeButton'; -import ProgressBar from 'features/system/components/ProgressBar'; -import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -import { memo, useCallback } from 'react'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { useTranslation } from 'react-i18next'; -import { FaPlay } from 'react-icons/fa'; - -interface InvokeButton - extends Omit {} - -const NodeInvokeButton = (props: InvokeButton) => { - const { ...rest } = props; - const dispatch = useAppDispatch(); - const activeTabName = useAppSelector(activeTabNameSelector); - const { isReady, isProcessing } = useIsReadyToInvoke(); - const handleInvoke = useCallback(() => { - dispatch(userInvoked('nodes')); - }, [dispatch]); - - const { t } = useTranslation(); - - useHotkeys( - ['ctrl+enter', 'meta+enter'], - handleInvoke, - { - enabled: () => isReady, - preventDefault: true, - enableOnFormTags: ['input', 'textarea', 'select'], - }, - [isReady, activeTabName] - ); - - return ( - - - {!isReady && ( - - - - )} - } - aria-label={t('parameters.invoke')} - type="submit" - isDisabled={!isReady} - onClick={handleInvoke} - flexGrow={1} - w="100%" - colorScheme="accent" - id="invoke-button" - leftIcon={isProcessing ? undefined : } - fontWeight={700} - isLoading={isProcessing} - loadingText={t('parameters.invoke')} - {...rest} - > - Invoke - - - - ); -}; - -export default memo(NodeInvokeButton); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton.tsx index cbb0ea58ee..ea51a57edd 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton.tsx @@ -1,5 +1,5 @@ import { useAppDispatch } from 'app/store/storeHooks'; -import IAIIconButton from 'common/components/IAIIconButton'; +import IAIButton from 'common/components/IAIButton'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { FaSyncAlt } from 'react-icons/fa'; @@ -14,12 +14,14 @@ const ReloadSchemaButton = () => { }, [dispatch]); return ( - } + } tooltip={t('nodes.reloadSchema')} aria-label={t('nodes.reloadSchema')} onClick={handleReloadSchema} - /> + > + {t('nodes.reloadSchema')} + ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/TopCenterPanel.tsx index 61a396498f..38fbbca397 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/TopCenterPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/TopCenterPanel.tsx @@ -1,24 +1,14 @@ -import { HStack } from '@chakra-ui/react'; -import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton'; +import { Flex } from '@chakra-ui/react'; import { memo } from 'react'; import { Panel } from 'reactflow'; -import NodeEditorSettings from './NodeEditorSettings'; -import ClearGraphButton from './ClearGraphButton'; -import NodeInvokeButton from './NodeInvokeButton'; -import ReloadSchemaButton from './ReloadSchemaButton'; -import LoadWorkflowButton from './LoadWorkflowButton'; +import WorkflowEditorControls from './WorkflowEditorControls'; const TopCenterPanel = () => { return ( - - - - - - - - + + + ); }; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/WorkflowEditorControls.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/WorkflowEditorControls.tsx new file mode 100644 index 0000000000..66815deb5a --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/WorkflowEditorControls.tsx @@ -0,0 +1,18 @@ +import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton'; +import InvokeButton from 'features/parameters/components/ProcessButtons/InvokeButton'; +import { memo } from 'react'; +import ClearGraphButton from './ClearGraphButton'; +import LoadWorkflowButton from './LoadWorkflowButton'; + +const WorkflowEditorControls = () => { + return ( + <> + + + + + + ); +}; + +export default memo(WorkflowEditorControls); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/TopRightPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/TopRightPanel.tsx index 903502811d..a57b01c150 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/TopRightPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/TopRightPanel.tsx @@ -2,6 +2,7 @@ import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; import { Panel } from 'reactflow'; import FieldTypeLegend from './FieldTypeLegend'; +import WorkflowEditorSettings from './WorkflowEditorSettings'; const TopRightPanel = () => { const shouldShowFieldTypeLegend = useAppSelector( @@ -10,6 +11,7 @@ const TopRightPanel = () => { return ( + {shouldShowFieldTypeLegend && } ); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeEditorSettings.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx similarity index 93% rename from invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeEditorSettings.tsx rename to invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx index 3e9e06e12e..20f33aba27 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopCenterPanel/NodeEditorSettings.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/panels/TopRightPanel/WorkflowEditorSettings.tsx @@ -27,6 +27,11 @@ import { import { ChangeEvent, memo, useCallback } from 'react'; import { FaCog } from 'react-icons/fa'; import { SelectionMode } from 'reactflow'; +import ReloadSchemaButton from '../TopCenterPanel/ReloadSchemaButton'; + +const formLabelProps: FormLabelProps = { + fontWeight: 600, +}; const selector = createSelector( stateSelector, @@ -49,7 +54,7 @@ const selector = createSelector( defaultSelectorOptions ); -const NodeEditorSettings = () => { +const WorkflowEditorSettings = () => { const { isOpen, onOpen, onClose } = useDisclosure(); const dispatch = useAppDispatch(); const { @@ -98,7 +103,8 @@ const NodeEditorSettings = () => { return ( <> } onClick={onOpen} /> @@ -106,7 +112,7 @@ const NodeEditorSettings = () => { - Node Editor Settings + Workflow Editor Settings { label="Validate Connections and Graph" helperText="Prevent invalid connections from being made, and invalid graphs from being invoked" /> + @@ -165,8 +172,4 @@ const NodeEditorSettings = () => { ); }; -export default memo(NodeEditorSettings); - -const formLabelProps: FormLabelProps = { - fontWeight: 600, -}; +export default memo(WorkflowEditorSettings); diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx b/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx index 7c29e77e12..7e42bcea5f 100644 --- a/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx +++ b/invokeai/frontend/web/src/features/nodes/hooks/useLoadWorkflowFromFile.tsx @@ -7,14 +7,11 @@ import { zWorkflow } from 'features/nodes/types/types'; import { addToast } from 'features/system/store/systemSlice'; import { makeToast } from 'features/system/util/makeToast'; import { memo, useCallback } from 'react'; -import { flushSync } from 'react-dom'; -import { useReactFlow } from 'reactflow'; import { ZodError } from 'zod'; import { fromZodError, fromZodIssue } from 'zod-validation-error'; export const useLoadWorkflowFromFile = () => { const dispatch = useAppDispatch(); - const { fitView } = useReactFlow(); const logger = useLogger('nodes'); const loadWorkflowFromFile = useCallback( (file: File | null) => { @@ -51,7 +48,6 @@ export const useLoadWorkflowFromFile = () => { } dispatch(workflowLoaded(result.data)); - flushSync(fitView); dispatch( addToast( @@ -79,7 +75,7 @@ export const useLoadWorkflowFromFile = () => { reader.readAsText(file); }, - [dispatch, fitView, logger] + [dispatch, logger] ); return loadWorkflowFromFile; diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx index 499392d5e0..c70f2528bb 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx @@ -1,6 +1,5 @@ import { Box, - ChakraProps, Divider, Flex, ListItem, @@ -19,7 +18,6 @@ import IAIIconButton, { import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke'; import { clampSymmetrySteps } from 'features/parameters/store/generationSlice'; import ProgressBar from 'features/system/components/ProgressBar'; -import { selectIsBusy } from 'features/system/store/systemSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; @@ -27,32 +25,6 @@ import { useTranslation } from 'react-i18next'; import { FaPlay } from 'react-icons/fa'; import { useBoardName } from 'services/api/hooks/useBoardName'; -const IN_PROGRESS_STYLES: ChakraProps['sx'] = { - _disabled: { - bg: 'none', - color: 'base.600', - cursor: 'not-allowed', - _hover: { - color: 'base.600', - bg: 'none', - }, - }, -}; - -const selector = createSelector( - [stateSelector, activeTabNameSelector, selectIsBusy], - ({ gallery }, activeTabName, isBusy) => { - const { autoAddBoardId } = gallery; - - return { - isBusy, - autoAddBoardId, - activeTabName, - }; - }, - defaultSelectorOptions -); - interface InvokeButton extends Omit { asIconButton?: boolean; @@ -62,7 +34,7 @@ export default function InvokeButton(props: InvokeButton) { const { asIconButton = false, sx, ...rest } = props; const dispatch = useAppDispatch(); const { isReady, isProcessing } = useIsReadyToInvoke(); - const { activeTabName } = useAppSelector(selector); + const activeTabName = useAppSelector(activeTabNameSelector); const handleInvoke = useCallback(() => { dispatch(clampSymmetrySteps()); @@ -113,10 +85,10 @@ export default function InvokeButton(props: InvokeButton) { colorScheme="accent" isLoading={isProcessing} id="invoke-button" + data-progress={isProcessing} sx={{ w: 'full', flexGrow: 1, - ...(isProcessing ? IN_PROGRESS_STYLES : {}), ...sx, }} {...rest} @@ -126,6 +98,7 @@ export default function InvokeButton(props: InvokeButton) { tooltip={} aria-label={t('parameters.invoke')} type="submit" + data-progress={isProcessing} isDisabled={!isReady} onClick={handleInvoke} colorScheme="accent" @@ -137,7 +110,6 @@ export default function InvokeButton(props: InvokeButton) { w: 'full', flexGrow: 1, fontWeight: 700, - ...(isProcessing ? IN_PROGRESS_STYLES : {}), ...sx, }} {...rest} @@ -150,9 +122,21 @@ export default function InvokeButton(props: InvokeButton) { ); } +const tooltipSelector = createSelector( + [stateSelector], + ({ gallery }) => { + const { autoAddBoardId } = gallery; + + return { + autoAddBoardId, + }; + }, + defaultSelectorOptions +); + export const InvokeButtonTooltipContent = memo(() => { const { isReady, reasons } = useIsReadyToInvoke(); - const { autoAddBoardId } = useAppSelector(selector); + const { autoAddBoardId } = useAppSelector(tooltipSelector); const autoAddBoardName = useBoardName(autoAddBoardId); return ( diff --git a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx index a81d898975..10349afd52 100644 --- a/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx +++ b/invokeai/frontend/web/src/features/system/components/InvokeAILogoComponent.tsx @@ -15,7 +15,7 @@ const InvokeAILogoComponent = ({ showVersion = true }: Props) => { const isHovered = useHoverDirty(ref); return ( - + invoke-ai-logo { if (c === 'base') { const _disabled = { bg: mode('base.150', 'base.700')(props), - color: mode('base.500', 'base.500')(props), + color: mode('base.300', 'base.500')(props), + svg: { + fill: mode('base.500', 'base.500')(props), + }, + opacity: 1, + }; + + const data_progress = { + bg: 'none', + color: mode('base.300', 'base.500')(props), svg: { fill: mode('base.500', 'base.500')(props), }, @@ -31,6 +40,7 @@ const invokeAI = defineStyle((props) => { _disabled, }, _disabled, + '&[data-progress="true"]': { ...data_progress, _hover: data_progress }, }; } @@ -45,6 +55,17 @@ const invokeAI = defineStyle((props) => { filter: mode(undefined, 'saturate(65%)')(props), }; + const data_progress = { + // bg: 'none', + color: mode(`${c}.50`, `${c}.500`)(props), + svg: { + fill: mode(`${c}.50`, `${c}.500`)(props), + filter: 'unset', + }, + opacity: 0.7, + filter: mode(undefined, 'saturate(65%)')(props), + }; + return { bg: mode(`${c}.400`, `${c}.600`)(props), color: mode(`base.50`, `base.100`)(props), @@ -61,6 +82,7 @@ const invokeAI = defineStyle((props) => { }, _disabled, }, + '&[data-progress="true"]': { ...data_progress, _hover: data_progress }, }; }); diff --git a/invokeai/frontend/web/src/theme/components/progress.ts b/invokeai/frontend/web/src/theme/components/progress.ts index 71231869ce..a2aebc8fb1 100644 --- a/invokeai/frontend/web/src/theme/components/progress.ts +++ b/invokeai/frontend/web/src/theme/components/progress.ts @@ -9,7 +9,7 @@ const { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(parts.keys); const invokeAIFilledTrack = defineStyle((_props) => ({ - bg: 'accentAlpha.500', + bg: 'accentAlpha.700', })); const invokeAITrack = defineStyle((_props) => {