feat(ui): revise app layout strategy, add interaction scopes for hotkeys

This commit is contained in:
psychedelicious
2024-08-18 23:37:49 +10:00
parent 50051ee147
commit 4c66a0dcd0
33 changed files with 807 additions and 613 deletions

View File

@@ -5,9 +5,6 @@ import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
import { LoadWorkflowFromGraphModal } from 'features/workflowLibrary/components/LoadWorkflowFromGraphModal/LoadWorkflowFromGraphModal';
import { SaveWorkflowAsDialog } from 'features/workflowLibrary/components/SaveWorkflowAsDialog/SaveWorkflowAsDialog';
import type { AnimationProps } from 'framer-motion';
import { AnimatePresence, motion } from 'framer-motion';
import type { CSSProperties } from 'react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { MdDeviceHub } from 'react-icons/md';
@@ -18,28 +15,6 @@ import { Flow } from './flow/Flow';
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
const isReadyMotionStyles: CSSProperties = {
position: 'relative',
width: '100%',
height: '100%',
};
const notIsReadyMotionStyles: CSSProperties = {
position: 'absolute',
width: '100%',
height: '100%',
};
const initial: AnimationProps['initial'] = {
opacity: 0,
};
const animate: AnimationProps['animate'] = {
opacity: 1,
transition: { duration: 0.2 },
};
const exit: AnimationProps['exit'] = {
opacity: 0,
transition: { duration: 0.2 },
};
const NodeEditor = () => {
const { data, isLoading } = useGetOpenAPISchemaQuery();
const { t } = useTranslation();
@@ -53,37 +28,18 @@ const NodeEditor = () => {
alignItems="center"
justifyContent="center"
>
<AnimatePresence>
{data && (
<motion.div initial={initial} animate={animate} exit={exit} style={isReadyMotionStyles}>
<Flow />
<AddNodePopover />
<TopPanel />
<BottomLeftPanel />
<MinimapPanel />
<SaveWorkflowAsDialog />
<LoadWorkflowFromGraphModal />
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{isLoading && (
<motion.div initial={initial} animate={animate} exit={exit} style={notIsReadyMotionStyles}>
<Flex
layerStyle="first"
position="relative"
width="full"
height="full"
borderRadius="base"
alignItems="center"
justifyContent="center"
pointerEvents="none"
>
<IAINoContentFallback label={t('nodes.loadingNodes')} icon={MdDeviceHub} />
</Flex>
</motion.div>
)}
</AnimatePresence>
{data && (
<>
<Flow />
<AddNodePopover />
<TopPanel />
<BottomLeftPanel />
<MinimapPanel />
<SaveWorkflowAsDialog />
<LoadWorkflowFromGraphModal />
</>
)}
{isLoading && <IAINoContentFallback label={t('nodes.loadingNodes')} icon={MdDeviceHub} />}
</Flex>
);
};

View File

@@ -5,6 +5,7 @@ import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } f
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
import type { SelectInstance } from 'chakra-react-select';
import { INTERACTION_SCOPES } from 'common/hooks/interactionScopes';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
import {
$cursorPos,
@@ -67,6 +68,7 @@ const AddNodePopover = () => {
const pendingConnection = useStore($pendingConnection);
const isOpen = useStore($isAddNodePopoverOpen);
const store = useAppStore();
const isWorkflowsActive = useStore(INTERACTION_SCOPES.workflows.$isActive);
const filteredTemplates = useMemo(() => {
// If we have a connection in progress, we need to filter the node choices
@@ -214,14 +216,7 @@ const AddNodePopover = () => {
}
}, []);
const handleHotkeyClose: HotkeyCallback = useCallback(() => {
if ($isAddNodePopoverOpen.get()) {
closeAddNodePopover();
}
}, []);
useHotkeys(['shift+a', 'space'], handleHotkeyOpen);
useHotkeys(['escape'], handleHotkeyClose, { enableOnFormTags: ['TEXTAREA'] });
useHotkeys(['shift+a', 'space'], handleHotkeyOpen, { enabled: isWorkflowsActive }, [isWorkflowsActive]);
const noOptionsMessage = useCallback(() => t('nodes.noMatchingNodes'), [t]);

View File

@@ -1,6 +1,7 @@
import { useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { INTERACTION_SCOPES, useScopeImperativeApi } from 'common/hooks/interactionScopes';
import { useConnection } from 'features/nodes/hooks/useConnection';
import { useCopyPaste } from 'features/nodes/hooks/useCopyPaste';
import { useSyncExecutionState } from 'features/nodes/hooks/useExecutionState';
@@ -79,16 +80,13 @@ export const Flow = memo(() => {
const cancelConnection = useReactFlowStore(selectCancelConnection);
const updateNodeInternals = useUpdateNodeInternals();
const store = useAppStore();
const isWorkflowsActive = useStore(INTERACTION_SCOPES.workflows.$isActive);
const workflowsScopeApi = useScopeImperativeApi('workflows');
useWorkflowWatcher();
useSyncExecutionState();
const [borderRadius] = useToken('radii', ['base']);
const flowStyles = useMemo<CSSProperties>(
() => ({
borderRadius,
}),
[borderRadius]
);
const flowStyles = useMemo<CSSProperties>(() => ({ borderRadius }), [borderRadius]);
const onNodesChange: OnNodesChange = useCallback(
(nodeChanges) => {
@@ -121,7 +119,8 @@ export const Flow = memo(() => {
const { onCloseGlobal } = useGlobalMenuClose();
const handlePaneClick = useCallback(() => {
onCloseGlobal();
}, [onCloseGlobal]);
workflowsScopeApi.add();
}, [onCloseGlobal, workflowsScopeApi]);
const onInit: OnInit = useCallback((flow) => {
$flow.set(flow);
@@ -237,7 +236,7 @@ export const Flow = memo(() => {
},
[dispatch, store]
);
useHotkeys(['Ctrl+a', 'Meta+a'], onSelectAllHotkey);
useHotkeys(['Ctrl+a', 'Meta+a'], onSelectAllHotkey, { enabled: isWorkflowsActive }, [isWorkflowsActive]);
const onPasteHotkey = useCallback(
(e: KeyboardEvent) => {