feat(ui): canvas launchpad

This commit is contained in:
psychedelicious
2025-06-19 19:28:45 +10:00
parent 6eecdca56c
commit fcaeba290e
14 changed files with 85 additions and 42 deletions

View File

@@ -26,7 +26,7 @@ import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
import { SelectObject } from 'features/controlLayers/components/SelectObject/SelectObject';
import { CanvasSessionContextProvider } from 'features/controlLayers/components/SimpleSession/context';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/InitialState';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { StagingAreaItemsList } from 'features/controlLayers/components/SimpleSession/StagingAreaItemsList';
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasToolbar';

View File

@@ -0,0 +1,36 @@
import { Button, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library';
import { memo } from 'react';
import { InitialStateMainModelPicker } from './InitialStateMainModelPicker';
import { LaunchpadAddStyleReference } from './LaunchpadAddStyleReference';
import { LaunchpadEditImageButton } from './LaunchpadEditImageButton';
import { LaunchpadGenerateFromTextButton } from './LaunchpadGenerateFromTextButton';
import { LaunchpadUseALayoutImageButton } from './LaunchpadUseALayoutImageButton';
export const CanvasLaunchpadPanel = memo(() => {
return (
<Flex flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" w="full" h="full" gap={4} px={12} maxW={768} pt="20%">
<Heading mb={4}>Get started with Invoke.</Heading>
<Flex flexDir="column" gap={8}>
<Grid gridTemplateColumns="1fr 1fr" gap={8}>
<InitialStateMainModelPicker />
<Flex flexDir="column" gap={2} justifyContent="center">
<Text>
Want to learn what prompts work best for each model?{' '}
<Button as="a" variant="link" href="#" size="sm">
Check our our Model Guide.
</Button>
</Text>
</Flex>
</Grid>
<LaunchpadGenerateFromTextButton />
<LaunchpadAddStyleReference />
<LaunchpadEditImageButton />
<LaunchpadUseALayoutImageButton />
</Flex>
</Flex>
</Flex>
);
});
CanvasLaunchpadPanel.displayName = 'CanvasLaunchpadPanel';

View File

@@ -1,11 +1,12 @@
import { Alert, Button, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { InitialStateAddAStyleReference } from 'features/controlLayers/components/SimpleSession/InitialStateAddAStyleReference';
import { InitialStateGenerateFromText } from 'features/controlLayers/components/SimpleSession/InitialStateGenerateFromText';
import { InitialStateMainModelPicker } from 'features/controlLayers/components/SimpleSession/InitialStateMainModelPicker';
import { LaunchpadAddStyleReference } from 'features/controlLayers/components/SimpleSession/LaunchpadAddStyleReference';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { memo, useCallback } from 'react';
import { LaunchpadGenerateFromTextButton } from './LaunchpadGenerateFromTextButton';
export const GenerateLaunchpadPanel = memo(() => {
const dispatch = useAppDispatch();
const newCanvasSession = useCallback(() => {
@@ -28,8 +29,8 @@ export const GenerateLaunchpadPanel = memo(() => {
</Text>
</Flex>
</Grid>
<InitialStateGenerateFromText />
<InitialStateAddAStyleReference />
<LaunchpadGenerateFromTextButton />
<LaunchpadAddStyleReference />
<Alert status="info" borderRadius="base" flexDir="column" gap={2} overflow="unset">
<Text fontSize="md" fontWeight="semibold">
Looking to get more control, edit, and iterate on your images?
@@ -43,4 +44,4 @@ export const GenerateLaunchpadPanel = memo(() => {
</Flex>
);
});
GenerateLaunchpadPanel.displayName = 'InitialState';
GenerateLaunchpadPanel.displayName = 'GenerateLaunchpad';

View File

@@ -1,7 +1,7 @@
import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library';
import { useAppStore } from 'app/store/nanostores/store';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { InitialStateButtonGridItem } from 'features/controlLayers/components/SimpleSession/InitialStateButtonGridItem';
import { LaunchpadButton } from 'features/controlLayers/components/SimpleSession/LaunchpadButton';
import { getDefaultRefImageConfig } from 'features/controlLayers/hooks/addLayerHooks';
import { refImageAdded } from 'features/controlLayers/store/refImagesSlice';
import { imageDTOToImageWithDims } from 'features/controlLayers/store/util';
@@ -13,7 +13,7 @@ import type { ImageDTO } from 'services/api/types';
const dndTargetData = addGlobalReferenceImageDndTarget.getData();
export const InitialStateAddAStyleReference = memo(() => {
export const LaunchpadAddStyleReference = memo(() => {
const { dispatch, getState } = useAppStore();
const uploadOptions = useMemo(
@@ -32,7 +32,7 @@ export const InitialStateAddAStyleReference = memo(() => {
const uploadApi = useImageUploadButton(uploadOptions);
return (
<InitialStateButtonGridItem {...uploadApi.getUploadButtonProps()} position="relative" gap={8}>
<LaunchpadButton {...uploadApi.getUploadButtonProps()} position="relative" gap={8}>
<Icon as={PiUserCircleGearBold} boxSize={8} color="base.500" />
<Flex flexDir="column" alignItems="flex-start" gap={2}>
<Heading size="sm">Add a Style Reference</Heading>
@@ -43,7 +43,7 @@ export const InitialStateAddAStyleReference = memo(() => {
<input {...uploadApi.getUploadInputProps()} />
</Flex>
<DndDropTarget dndTarget={newCanvasFromImageDndTarget} dndTargetData={dndTargetData} label="Drop" />
</InitialStateButtonGridItem>
</LaunchpadButton>
);
});
InitialStateAddAStyleReference.displayName = 'InitialStateAddAStyleReference';
LaunchpadAddStyleReference.displayName = 'LaunchpadAddStyleReference';

View File

@@ -2,7 +2,7 @@ import type { ButtonProps } from '@invoke-ai/ui-library';
import { Button, forwardRef } from '@invoke-ai/ui-library';
import { memo } from 'react';
export const InitialStateButtonGridItem = memo(
export const LaunchpadButton = memo(
forwardRef(({ children, ...rest }: ButtonProps, ref) => {
return (
<Button
@@ -26,4 +26,4 @@ export const InitialStateButtonGridItem = memo(
})
);
InitialStateButtonGridItem.displayName = 'InitialStateButtonGridItem';
LaunchpadButton.displayName = 'LaunchpadButton';

View File

@@ -1,7 +1,7 @@
import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library';
import { useAppStore } from 'app/store/nanostores/store';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { InitialStateButtonGridItem } from 'features/controlLayers/components/SimpleSession/InitialStateButtonGridItem';
import { LaunchpadButton } from 'features/controlLayers/components/SimpleSession/LaunchpadButton';
import { newCanvasFromImageDndTarget } from 'features/dnd/dnd';
import { DndDropTarget } from 'features/dnd/DndDropTarget';
import { newCanvasFromImage } from 'features/imageActions/actions';
@@ -13,7 +13,7 @@ const NEW_CANVAS_OPTIONS = { type: 'raster_layer', withInpaintMask: true } as co
const dndTargetData = newCanvasFromImageDndTarget.getData(NEW_CANVAS_OPTIONS);
export const InitialStateEditImageCard = memo(() => {
export const LaunchpadEditImageButton = memo(() => {
const { getState, dispatch } = useAppStore();
const onUpload = useCallback(
@@ -25,16 +25,18 @@ export const InitialStateEditImageCard = memo(() => {
const uploadApi = useImageUploadButton({ allowMultiple: false, onUpload });
return (
<InitialStateButtonGridItem {...uploadApi.getUploadButtonProps()}>
<LaunchpadButton {...uploadApi.getUploadButtonProps()} position="relative" gap={8}>
<Icon as={PiPencilBold} boxSize={8} color="base.500" />
<Heading size="sm">Edit Image</Heading>
<Text color="base.300">Add an image to refine.</Text>
<Flex w="full" justifyContent="flex-end" p={2}>
<Flex flexDir="column" alignItems="flex-start" gap={2}>
<Heading size="sm">Edit Image</Heading>
<Text color="base.300">Add an image to refine.</Text>
</Flex>
<Flex position="absolute" right={3} bottom={3}>
<PiUploadBold />
<input {...uploadApi.getUploadInputProps()} />
</Flex>
<DndDropTarget dndTarget={newCanvasFromImageDndTarget} dndTargetData={dndTargetData} label="Drop" />
</InitialStateButtonGridItem>
</LaunchpadButton>
);
});
InitialStateEditImageCard.displayName = 'InitialStateEditImageCard';
LaunchpadEditImageButton.displayName = 'LaunchpadEditImageButton';

View File

@@ -1,5 +1,5 @@
import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library';
import { InitialStateButtonGridItem } from 'features/controlLayers/components/SimpleSession/InitialStateButtonGridItem';
import { LaunchpadButton } from 'features/controlLayers/components/SimpleSession/LaunchpadButton';
import { memo } from 'react';
import { PiCursorTextBold, PiTextAaBold } from 'react-icons/pi';
@@ -11,9 +11,9 @@ const focusOnPrompt = () => {
}
};
export const InitialStateGenerateFromText = memo(() => {
export const LaunchpadGenerateFromTextButton = memo(() => {
return (
<InitialStateButtonGridItem onClick={focusOnPrompt} position="relative" gap={8}>
<LaunchpadButton onClick={focusOnPrompt} position="relative" gap={8}>
<Icon as={PiTextAaBold} boxSize={8} color="base.500" />
<Flex flexDir="column" alignItems="flex-start" gap={2}>
<Heading size="sm">Generate from Text</Heading>
@@ -22,7 +22,7 @@ export const InitialStateGenerateFromText = memo(() => {
<Flex position="absolute" right={3} bottom={3}>
<PiCursorTextBold />
</Flex>
</InitialStateButtonGridItem>
</LaunchpadButton>
);
});
InitialStateGenerateFromText.displayName = 'InitialStateGenerateFromText';
LaunchpadGenerateFromTextButton.displayName = 'LaunchpadGenerateFromTextButton';

View File

@@ -1,7 +1,6 @@
import { Flex, Heading, Icon, Text } from '@invoke-ai/ui-library';
import { useAppStore } from 'app/store/nanostores/store';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { InitialStateButtonGridItem } from 'features/controlLayers/components/SimpleSession/InitialStateButtonGridItem';
import { newCanvasFromImageDndTarget } from 'features/dnd/dnd';
import { DndDropTarget } from 'features/dnd/DndDropTarget';
import { newCanvasFromImage } from 'features/imageActions/actions';
@@ -9,11 +8,13 @@ import { memo, useCallback } from 'react';
import { PiRectangleDashedBold, PiUploadBold } from 'react-icons/pi';
import type { ImageDTO } from 'services/api/types';
import { LaunchpadButton } from './LaunchpadButton';
const NEW_CANVAS_OPTIONS = { type: 'control_layer', withResize: true } as const;
const dndTargetData = newCanvasFromImageDndTarget.getData(NEW_CANVAS_OPTIONS);
export const InitialStateUseALayoutImageCard = memo(() => {
export const LaunchpadUseALayoutImageButton = memo(() => {
const { getState, dispatch } = useAppStore();
const onUpload = useCallback(
@@ -25,16 +26,18 @@ export const InitialStateUseALayoutImageCard = memo(() => {
const uploadApi = useImageUploadButton({ allowMultiple: false, onUpload });
return (
<InitialStateButtonGridItem {...uploadApi.getUploadButtonProps()}>
<LaunchpadButton {...uploadApi.getUploadButtonProps()} position="relative" gap={8}>
<Icon as={PiRectangleDashedBold} boxSize={8} color="base.500" />
<Heading size="sm">Use a Layout Image</Heading>
<Text color="base.300">Add an image to control composition.</Text>
<Flex w="full" justifyContent="flex-end" p={2}>
<Flex flexDir="column" alignItems="flex-start" gap={2}>
<Heading size="sm">Use a Layout Image</Heading>
<Text color="base.300">Add an image to control composition.</Text>
</Flex>
<Flex position="absolute" right={3} bottom={3}>
<PiUploadBold />
<input {...uploadApi.getUploadInputProps()} />
</Flex>
<DndDropTarget dndTarget={newCanvasFromImageDndTarget} dndTargetData={dndTargetData} label="Drop" />
</InitialStateButtonGridItem>
</LaunchpadButton>
);
});
InitialStateUseALayoutImageCard.displayName = 'InitialStateUseALayoutImageCard';
LaunchpadUseALayoutImageButton.displayName = 'LaunchpadUseALayoutImageButton';

View File

@@ -1,6 +1,6 @@
import { Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/InitialState';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer2';
import { ProgressImage } from 'features/gallery/components/ImageViewer/ProgressImage2';
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar2';

View File

@@ -14,14 +14,14 @@ export const CanvasToolbarResetViewButton = memo(() => {
useRegisteredHotkeys({
id: 'fitLayersToCanvas',
category: 'canvas',
callback: canvasManager.stage.fitLayersToStage,
callback: () => canvasManager.stage.fitLayersToStage(),
options: { enabled: isCanvasFocused, preventDefault: true },
dependencies: [isCanvasFocused],
});
useRegisteredHotkeys({
id: 'fitBboxToCanvas',
category: 'canvas',
callback: canvasManager.stage.fitBboxToStage,
callback: () => canvasManager.stage.fitBboxToStage(),
options: { enabled: isCanvasFocused, preventDefault: true },
dependencies: [isCanvasFocused],
});

View File

@@ -3,7 +3,8 @@ import { useAppSelector } from 'app/store/storeHooks';
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, Orientation } from 'dockview';
import { CanvasLayersPanel } from 'features/controlLayers/components/CanvasLayersPanelContent';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/InitialState';
import { CanvasLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/CanvasLaunchpadPanel';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
import { GalleryPanel } from 'features/gallery/components/Gallery';
import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer/GenerationProgressPanel';
@@ -30,7 +31,7 @@ export const dockviewComponents: IDockviewReactProps['components'] = {
// Workflows tab
WorkflowsLaunchpad: GenerateLaunchpadPanel,
// Canvas tab
CanvasLaunchpad: GenerateLaunchpadPanel,
CanvasLaunchpad: CanvasLaunchpadPanel,
CanvasWorkspace: CanvasWorkspacePanel,
};

View File

@@ -15,7 +15,7 @@ import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
import { SelectObject } from 'features/controlLayers/components/SelectObject/SelectObject';
import { CanvasSessionContextProvider } from 'features/controlLayers/components/SimpleSession/context';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/InitialState';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { StagingAreaItemsList } from 'features/controlLayers/components/SimpleSession/StagingAreaItemsList';
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasToolbar';

View File

@@ -1,6 +1,6 @@
import type { IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { CanvasLayersPanel } from 'features/controlLayers/components/CanvasLayersPanelContent';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/InitialState';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
import { GalleryPanel } from 'features/gallery/components/Gallery';
import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer/GenerationProgressPanel';

View File

@@ -2,7 +2,7 @@ import { Box, Divider, Flex } from '@invoke-ai/ui-library';
import { $isLayoutLoading } from 'app/store/nanostores/globalIsLoading';
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, Orientation } from 'dockview';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/InitialState';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
import { GalleryPanel } from 'features/gallery/components/Gallery';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer2';