Skip to content

Commit 3f18bfe

Browse files
feat(ui): add loading state for builder
1 parent 012054a commit 3f18bfe

File tree

4 files changed

+37
-11
lines changed

4 files changed

+37
-11
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1004,6 +1004,7 @@
10041004
"unknownOutput": "Unknown output: {{name}}",
10051005
"updateNode": "Update Node",
10061006
"updateApp": "Update App",
1007+
"loadingTemplates": "Loading {{name}}",
10071008
"updateAllNodes": "Update Nodes",
10081009
"allNodesUpdated": "All Nodes Updated",
10091010
"unableToUpdateNodes_one": "Unable to update {{count}} node",

invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/WorkflowBuilder.tsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
22
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
33
import type { SystemStyleObject } from '@invoke-ai/ui-library';
44
import { Button, Flex, Spacer } from '@invoke-ai/ui-library';
5+
import { useStore } from '@nanostores/react';
56
import { useAppSelector } from 'app/store/storeHooks';
7+
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
68
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
79
import { firefoxDndFix } from 'features/dnd/util';
810
import { FormElementComponent } from 'features/nodes/components/sidePanel/builder/ContainerElementComponent';
911
import { buildFormElementDndData, useBuilderDndMonitor } from 'features/nodes/components/sidePanel/builder/dnd-hooks';
1012
import { WorkflowBuilderEditMenu } from 'features/nodes/components/sidePanel/builder/WorkflowBuilderMenu';
13+
import { $hasTemplates } from 'features/nodes/store/nodesSlice';
1114
import { selectFormRootElementId, selectIsFormEmpty } from 'features/nodes/store/workflowSlice';
1215
import type { FormElement } from 'features/nodes/types/workflow';
1316
import { buildContainer, buildDivider, buildHeading, buildText } from 'features/nodes/types/workflow';
1417
import { startCase } from 'lodash-es';
1518
import type { RefObject } from 'react';
1619
import { memo, useEffect, useRef, useState } from 'react';
1720
import { useTranslation } from 'react-i18next';
21+
import { useGetOpenAPISchemaQuery } from 'services/api/endpoints/appInfo';
1822
import { assert } from 'tsafe';
1923

2024
const sx: SystemStyleObject = {
@@ -28,8 +32,7 @@ const sx: SystemStyleObject = {
2832

2933
export const WorkflowBuilder = memo(() => {
3034
const { t } = useTranslation();
31-
const rootElementId = useAppSelector(selectFormRootElementId);
32-
const isFormEmpty = useAppSelector(selectIsFormEmpty);
35+
3336
useBuilderDndMonitor();
3437

3538
return (
@@ -47,16 +50,33 @@ export const WorkflowBuilder = memo(() => {
4750
<WorkflowBuilderEditMenu />
4851
</Flex>
4952
<ScrollableContent>
50-
<Flex sx={sx} data-is-empty={isFormEmpty}>
51-
<FormElementComponent id={rootElementId} />
52-
</Flex>
53+
<WorkflowBuilderContent />
5354
</ScrollableContent>
5455
</Flex>
5556
</Flex>
5657
);
5758
});
5859
WorkflowBuilder.displayName = 'WorkflowBuilder';
5960

61+
const WorkflowBuilderContent = memo(() => {
62+
const { t } = useTranslation();
63+
const rootElementId = useAppSelector(selectFormRootElementId);
64+
const isFormEmpty = useAppSelector(selectIsFormEmpty);
65+
const openApiSchemaQuery = useGetOpenAPISchemaQuery();
66+
const loadedTemplates = useStore($hasTemplates);
67+
68+
if (openApiSchemaQuery.isLoading || !loadedTemplates) {
69+
return <IAINoContentFallback label={t('nodes.loadingNodes')} icon={null} />;
70+
}
71+
72+
return (
73+
<Flex sx={sx} data-is-empty={isFormEmpty}>
74+
<FormElementComponent id={rootElementId} />
75+
</Flex>
76+
);
77+
});
78+
WorkflowBuilderContent.displayName = 'WorkflowBuilderContent';
79+
6080
const useAddFormElementDnd = (
6181
type: Exclude<FormElement['type'], 'node-field'>,
6282
draggableRef: RefObject<HTMLElement>

invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/EmptyState.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const EmptyState = () => {
1010
const isCleanEditor = useAppSelector(selectCleanEditor);
1111

1212
return (
13-
<Flex w="full" userSelect="none" justifyContent="center">
13+
<Flex w="full" h="full" userSelect="none" justifyContent="center">
1414
<Flex
1515
alignItems="center"
1616
justifyContent="center"
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Box, Flex } from '@invoke-ai/ui-library';
2+
import { useStore } from '@nanostores/react';
23
import { useAppSelector } from 'app/store/storeHooks';
34
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
45
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
56
import { FormElementComponent } from 'features/nodes/components/sidePanel/builder/ContainerElementComponent';
67
import { EmptyState } from 'features/nodes/components/sidePanel/viewMode/EmptyState';
8+
import { $hasTemplates } from 'features/nodes/store/nodesSlice';
79
import { selectFormRootElementId, selectIsFormEmpty } from 'features/nodes/store/workflowSlice';
810
import { t } from 'i18next';
911
import { memo } from 'react';
@@ -13,9 +15,7 @@ export const ViewModeLeftPanelContent = memo(() => {
1315
return (
1416
<Box position="relative" w="full" h="full">
1517
<ScrollableContent>
16-
<Flex flexDir="column" w="full" maxW="768px">
17-
<ViewModeLeftPanelContentInner />
18-
</Flex>
18+
<ViewModeLeftPanelContentInner />
1919
</ScrollableContent>
2020
</Box>
2121
);
@@ -24,17 +24,22 @@ ViewModeLeftPanelContent.displayName = 'ViewModeLeftPanelContent';
2424

2525
const ViewModeLeftPanelContentInner = memo(() => {
2626
const { isLoading } = useGetOpenAPISchemaQuery();
27+
const loadedTemplates = useStore($hasTemplates);
2728
const rootElementId = useAppSelector(selectFormRootElementId);
2829
const isFormEmpty = useAppSelector(selectIsFormEmpty);
2930

30-
if (isLoading) {
31+
if (isLoading || !loadedTemplates) {
3132
return <IAINoContentFallback label={t('nodes.loadingNodes')} icon={null} />;
3233
}
3334

3435
if (isFormEmpty) {
3536
return <EmptyState />;
3637
}
3738

38-
return <FormElementComponent id={rootElementId} />;
39+
return (
40+
<Flex flexDir="column" w="full" maxW="768px">
41+
<FormElementComponent id={rootElementId} />
42+
</Flex>
43+
);
3944
});
4045
ViewModeLeftPanelContentInner.displayName = ' ViewModeLeftPanelContentInner';

0 commit comments

Comments
 (0)