mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip layouting
This commit is contained in:
parent
3dc60254b9
commit
5365f42a04
@ -89,6 +89,7 @@
|
|||||||
"react-konva": "^18.2.7",
|
"react-konva": "^18.2.7",
|
||||||
"react-konva-utils": "^1.0.4",
|
"react-konva-utils": "^1.0.4",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
|
"react-resizable-panels": "^0.0.42",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import ImageUploader from 'common/components/ImageUploader';
|
import ImageUploader from 'common/components/ImageUploader';
|
||||||
import ProgressBar from 'features/system/components/ProgressBar';
|
|
||||||
import SiteHeader from 'features/system/components/SiteHeader';
|
import SiteHeader from 'features/system/components/SiteHeader';
|
||||||
|
import ProgressBar from 'features/system/components/ProgressBar';
|
||||||
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
import InvokeTabs from 'features/ui/components/InvokeTabs';
|
||||||
|
|
||||||
import useToastWatcher from 'features/system/hooks/useToastWatcher';
|
import useToastWatcher from 'features/system/hooks/useToastWatcher';
|
||||||
@ -28,6 +28,9 @@ import { configChanged } from 'features/system/store/configSlice';
|
|||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { useLogger } from 'app/logging/useLogger';
|
import { useLogger } from 'app/logging/useLogger';
|
||||||
import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview';
|
import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview';
|
||||||
|
import ResizableDrawer from 'features/ui/components/common/ResizableDrawer/ResizableDrawer';
|
||||||
|
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
|
||||||
|
import CreateParametersDrawer from 'features/ui/components/CreateParametersDrawer';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -84,11 +87,13 @@ const App = ({ config = DEFAULT_CONFIG, children }: Props) => {
|
|||||||
flexDir={{ base: 'column', xl: 'row' }}
|
flexDir={{ base: 'column', xl: 'row' }}
|
||||||
>
|
>
|
||||||
<InvokeTabs />
|
<InvokeTabs />
|
||||||
<ImageGalleryPanel />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ImageUploader>
|
</ImageUploader>
|
||||||
|
|
||||||
|
<ImageGalleryPanel />
|
||||||
|
<CreateParametersDrawer />
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{!isApplicationReady && !loadingOverridden && (
|
{!isApplicationReady && !loadingOverridden && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
@ -2,6 +2,12 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import {
|
||||||
|
toggleGalleryPanel,
|
||||||
|
toggleParametersPanel,
|
||||||
|
togglePinGalleryPanel,
|
||||||
|
togglePinParametersPanel,
|
||||||
|
} from 'features/ui/store/uiSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
|
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
|
||||||
|
|
||||||
@ -36,4 +42,20 @@ export const useGlobalHotkeys = () => {
|
|||||||
{ keyup: true, keydown: true },
|
{ keyup: true, keydown: true },
|
||||||
[shift]
|
[shift]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useHotkeys('o', () => {
|
||||||
|
dispatch(toggleParametersPanel());
|
||||||
|
});
|
||||||
|
|
||||||
|
useHotkeys(['shift+o'], () => {
|
||||||
|
dispatch(togglePinParametersPanel());
|
||||||
|
});
|
||||||
|
|
||||||
|
useHotkeys('g', () => {
|
||||||
|
dispatch(toggleGalleryPanel());
|
||||||
|
});
|
||||||
|
|
||||||
|
useHotkeys(['shift+g'], () => {
|
||||||
|
dispatch(togglePinGalleryPanel());
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -73,7 +73,7 @@ const galleryPanelSelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ImageGalleryPanel = () => {
|
const ImageGalleryPanel = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
shouldPinGallery,
|
shouldPinGallery,
|
||||||
@ -102,21 +102,21 @@ export const ImageGalleryPanel = () => {
|
|||||||
|
|
||||||
const resolution = useResolution();
|
const resolution = useResolution();
|
||||||
|
|
||||||
useHotkeys(
|
// useHotkeys(
|
||||||
'g',
|
// 'g',
|
||||||
() => {
|
// () => {
|
||||||
handleToggleGallery();
|
// handleToggleGallery();
|
||||||
},
|
// },
|
||||||
[shouldPinGallery]
|
// [shouldPinGallery]
|
||||||
);
|
// );
|
||||||
|
|
||||||
useHotkeys(
|
// useHotkeys(
|
||||||
'shift+g',
|
// 'shift+g',
|
||||||
() => {
|
// () => {
|
||||||
handleSetShouldPinGallery();
|
// handleSetShouldPinGallery();
|
||||||
},
|
// },
|
||||||
[shouldPinGallery]
|
// [shouldPinGallery]
|
||||||
);
|
// );
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'esc',
|
'esc',
|
||||||
@ -162,55 +162,71 @@ export const ImageGalleryPanel = () => {
|
|||||||
[galleryImageMinimumWidth]
|
[galleryImageMinimumWidth]
|
||||||
);
|
);
|
||||||
|
|
||||||
const calcGalleryMinHeight = () => {
|
// const calcGalleryMinHeight = () => {
|
||||||
if (resolution === 'desktop') return;
|
// if (resolution === 'desktop') return;
|
||||||
return 300;
|
// return 300;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const imageGalleryContent = () => {
|
// const imageGalleryContent = () => {
|
||||||
return (
|
// return (
|
||||||
<Flex
|
// <Flex
|
||||||
w="100vw"
|
// w="100vw"
|
||||||
h={{ base: 300, xl: '100vh' }}
|
// h={{ base: 300, xl: '100vh' }}
|
||||||
paddingRight={{ base: 8, xl: 0 }}
|
// paddingRight={{ base: 8, xl: 0 }}
|
||||||
paddingBottom={{ base: 4, xl: 0 }}
|
// paddingBottom={{ base: 4, xl: 0 }}
|
||||||
>
|
// >
|
||||||
<ImageGalleryContent />
|
// <ImageGalleryContent />
|
||||||
</Flex>
|
// </Flex>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
// const resizableImageGalleryContent = () => {
|
||||||
|
// return (
|
||||||
|
// <ResizableDrawer
|
||||||
|
// direction="right"
|
||||||
|
// isResizable={isResizable || !shouldPinGallery}
|
||||||
|
// isOpen={shouldShowGallery}
|
||||||
|
// onClose={handleCloseGallery}
|
||||||
|
// isPinned={shouldPinGallery && !isLightboxOpen}
|
||||||
|
// minWidth={
|
||||||
|
// shouldPinGallery
|
||||||
|
// ? GALLERY_TAB_WIDTHS[activeTabName].galleryMinWidth
|
||||||
|
// : 200
|
||||||
|
// }
|
||||||
|
// maxWidth={
|
||||||
|
// shouldPinGallery
|
||||||
|
// ? GALLERY_TAB_WIDTHS[activeTabName].galleryMaxWidth
|
||||||
|
// : undefined
|
||||||
|
// }
|
||||||
|
// minHeight={calcGalleryMinHeight()}
|
||||||
|
// >
|
||||||
|
// <ImageGalleryContent />
|
||||||
|
// </ResizableDrawer>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const renderImageGallery = () => {
|
||||||
|
// if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent();
|
||||||
|
// return resizableImageGalleryContent();
|
||||||
|
// };
|
||||||
|
|
||||||
|
if (shouldPinGallery) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const resizableImageGalleryContent = () => {
|
|
||||||
return (
|
return (
|
||||||
<ResizableDrawer
|
<ResizableDrawer
|
||||||
direction="right"
|
direction="right"
|
||||||
isResizable={isResizable || !shouldPinGallery}
|
isResizable={true}
|
||||||
isOpen={shouldShowGallery}
|
isOpen={shouldShowGallery}
|
||||||
onClose={handleCloseGallery}
|
onClose={handleCloseGallery}
|
||||||
isPinned={shouldPinGallery && !isLightboxOpen}
|
minWidth={200}
|
||||||
minWidth={
|
|
||||||
shouldPinGallery
|
|
||||||
? GALLERY_TAB_WIDTHS[activeTabName].galleryMinWidth
|
|
||||||
: 200
|
|
||||||
}
|
|
||||||
maxWidth={
|
|
||||||
shouldPinGallery
|
|
||||||
? GALLERY_TAB_WIDTHS[activeTabName].galleryMaxWidth
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
minHeight={calcGalleryMinHeight()}
|
|
||||||
>
|
>
|
||||||
<ImageGalleryContent />
|
<ImageGalleryContent />
|
||||||
</ResizableDrawer>
|
</ResizableDrawer>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
const renderImageGallery = () => {
|
// return renderImageGallery();
|
||||||
if (['mobile', 'tablet'].includes(resolution)) return imageGalleryContent();
|
|
||||||
return resizableImageGalleryContent();
|
|
||||||
};
|
|
||||||
|
|
||||||
return renderImageGallery();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ImageGalleryPanel);
|
export default memo(ImageGalleryPanel);
|
||||||
|
@ -10,7 +10,6 @@ import ConditioningInputFieldComponent from './fields/ConditioningInputFieldComp
|
|||||||
import ModelInputFieldComponent from './fields/ModelInputFieldComponent';
|
import ModelInputFieldComponent from './fields/ModelInputFieldComponent';
|
||||||
import NumberInputFieldComponent from './fields/NumberInputFieldComponent';
|
import NumberInputFieldComponent from './fields/NumberInputFieldComponent';
|
||||||
import StringInputFieldComponent from './fields/StringInputFieldComponent';
|
import StringInputFieldComponent from './fields/StringInputFieldComponent';
|
||||||
import ItemInputFieldComponent from './fields/ItemInputFieldComponent';
|
|
||||||
import ColorInputFieldComponent from './fields/ColorInputFieldComponent';
|
import ColorInputFieldComponent from './fields/ColorInputFieldComponent';
|
||||||
import ItemInputFieldComponent from './fields/ItemInputFieldComponent';
|
import ItemInputFieldComponent from './fields/ItemInputFieldComponent';
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const AnimatedImageToImagePanel = () => {
|
|||||||
exit={{ opacity: 0, scale: 0, width: 0 }}
|
exit={{ opacity: 0, scale: 0, width: 0 }}
|
||||||
transition={{ type: 'spring', bounce: 0, duration: 0.35 }}
|
transition={{ type: 'spring', bounce: 0, duration: 0.35 }}
|
||||||
>
|
>
|
||||||
<Box sx={{ h: 'full', w: 'full', pl: 4 }}>
|
<Box sx={{ h: 'full', w: 'full' }}>
|
||||||
<ImageToImageSettings />
|
<ImageToImageSettings />
|
||||||
</Box>
|
</Box>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
CancelStrategy,
|
CancelStrategy,
|
||||||
} from 'features/system/store/systemSlice';
|
} from 'features/system/store/systemSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useCallback, memo } from 'react';
|
import { useCallback, memo, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
ButtonSpinner,
|
ButtonSpinner,
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
@ -102,39 +102,39 @@ const CancelButton = (
|
|||||||
[isConnected, isProcessing, isCancelable]
|
[isConnected, isProcessing, isCancelable]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cancelLabel = useMemo(() => {
|
||||||
|
if (isCancelScheduled) {
|
||||||
|
return t('parameters.cancel.isScheduled');
|
||||||
|
}
|
||||||
|
if (cancelType === 'immediate') {
|
||||||
|
return t('parameters.cancel.immediate');
|
||||||
|
}
|
||||||
|
|
||||||
|
return t('parameters.cancel.schedule');
|
||||||
|
}, [t, cancelType, isCancelScheduled]);
|
||||||
|
|
||||||
|
const cancelIcon = useMemo(() => {
|
||||||
|
if (isCancelScheduled) {
|
||||||
|
return <ButtonSpinner />;
|
||||||
|
}
|
||||||
|
if (cancelType === 'immediate') {
|
||||||
|
return <MdCancel />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <MdCancelScheduleSend />;
|
||||||
|
}, [cancelType, isCancelScheduled]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached width={btnGroupWidth}>
|
<ButtonGroup isAttached width={btnGroupWidth}>
|
||||||
{cancelType === 'immediate' ? (
|
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<MdCancel />}
|
icon={cancelIcon}
|
||||||
tooltip={t('parameters.cancel.immediate')}
|
tooltip={cancelLabel}
|
||||||
aria-label={t('parameters.cancel.immediate')}
|
aria-label={cancelLabel}
|
||||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
||||||
onClick={handleClickCancel}
|
onClick={handleClickCancel}
|
||||||
colorScheme="error"
|
colorScheme="error"
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<IAIIconButton
|
|
||||||
icon={
|
|
||||||
isCancelScheduled ? <ButtonSpinner /> : <MdCancelScheduleSend />
|
|
||||||
}
|
|
||||||
tooltip={
|
|
||||||
isCancelScheduled
|
|
||||||
? t('parameters.cancel.isScheduled')
|
|
||||||
: t('parameters.cancel.schedule')
|
|
||||||
}
|
|
||||||
aria-label={
|
|
||||||
isCancelScheduled
|
|
||||||
? t('parameters.cancel.isScheduled')
|
|
||||||
: t('parameters.cancel.schedule')
|
|
||||||
}
|
|
||||||
isDisabled={!isConnected || !isProcessing || !isCancelable}
|
|
||||||
onClick={handleClickCancel}
|
|
||||||
colorScheme="error"
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Menu closeOnSelect={false}>
|
<Menu closeOnSelect={false}>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
import ResizableDrawer from './common/ResizableDrawer/ResizableDrawer';
|
||||||
|
import GenerateParameters from './tabs/Create/GenerateParameters';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { activeTabNameSelector, uiSelector } from '../store/uiSelectors';
|
||||||
|
import { lightboxSelector } from 'features/lightbox/store/lightboxSelectors';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import {
|
||||||
|
setShouldShowParametersPanel,
|
||||||
|
toggleParametersPanel,
|
||||||
|
} from '../store/uiSlice';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { Flex } from '@chakra-ui/react';
|
||||||
|
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
|
||||||
|
import PinParametersPanelButton from './PinParametersPanelButton';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[uiSelector, activeTabNameSelector, lightboxSelector],
|
||||||
|
(ui, activeTabName, lightbox) => {
|
||||||
|
const { shouldPinParametersPanel, shouldShowParametersPanel } = ui;
|
||||||
|
const { isLightboxOpen } = lightbox;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
memoizeOptions: {
|
||||||
|
resultEqualityCheck: isEqual,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const CreateParametersPanel = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { shouldPinParametersPanel, shouldShowParametersPanel } =
|
||||||
|
useAppSelector(selector);
|
||||||
|
|
||||||
|
const handleClosePanel = () => {
|
||||||
|
dispatch(setShouldShowParametersPanel(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (shouldPinParametersPanel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ResizableDrawer
|
||||||
|
direction="left"
|
||||||
|
isResizable={true}
|
||||||
|
isOpen={shouldShowParametersPanel}
|
||||||
|
onClose={handleClosePanel}
|
||||||
|
minWidth={500}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
flexDir="column"
|
||||||
|
position="relative"
|
||||||
|
h={{ base: 600, xl: 'full' }}
|
||||||
|
w={{ sm: 'full', lg: '100vw', xl: 'full' }}
|
||||||
|
paddingRight={{ base: 8, xl: 0 }}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
paddingTop={1.5}
|
||||||
|
paddingBottom={4}
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<InvokeAILogoComponent />
|
||||||
|
<PinParametersPanelButton />
|
||||||
|
</Flex>
|
||||||
|
<GenerateParameters />
|
||||||
|
</Flex>
|
||||||
|
</ResizableDrawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CreateParametersPanel);
|
@ -16,7 +16,7 @@ const floatingGalleryButtonSelector = createSelector(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
shouldPinGallery,
|
shouldPinGallery,
|
||||||
shouldShowGalleryButton: !shouldPinGallery || !shouldShowGallery,
|
shouldShowGalleryButton: !shouldShowGallery,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
{ memoizeOptions: { resultEqualityCheck: isEqual } }
|
{ memoizeOptions: { resultEqualityCheck: isEqual } }
|
||||||
|
@ -39,7 +39,7 @@ export const floatingParametersPanelButtonSelector = createSelector(
|
|||||||
|
|
||||||
const shouldShowParametersPanelButton =
|
const shouldShowParametersPanelButton =
|
||||||
!canvasBetaLayoutCheck &&
|
!canvasBetaLayoutCheck &&
|
||||||
(!shouldPinParametersPanel || !shouldShowParametersPanel) &&
|
!shouldShowParametersPanel &&
|
||||||
['generate', 'unifiedCanvas'].includes(activeTabName);
|
['generate', 'unifiedCanvas'].includes(activeTabName);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -65,7 +65,11 @@ const FloatingParametersPanelButtons = () => {
|
|||||||
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
||||||
};
|
};
|
||||||
|
|
||||||
return shouldShowParametersPanelButton ? (
|
if (!shouldShowParametersPanelButton) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
pos="absolute"
|
pos="absolute"
|
||||||
transform="translate(0, -50%)"
|
transform="translate(0, -50%)"
|
||||||
@ -92,7 +96,7 @@ const FloatingParametersPanelButtons = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
) : null;
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(FloatingParametersPanelButtons);
|
export default memo(FloatingParametersPanelButtons);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
ChakraProps,
|
ChakraProps,
|
||||||
|
Flex,
|
||||||
Icon,
|
Icon,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
@ -14,7 +16,14 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
|
import { setIsLightboxOpen } from 'features/lightbox/store/lightboxSlice';
|
||||||
import { InvokeTabName } from 'features/ui/store/tabMap';
|
import { InvokeTabName } from 'features/ui/store/tabMap';
|
||||||
import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice';
|
import { setActiveTab, togglePanels } from 'features/ui/store/uiSlice';
|
||||||
import { memo, ReactNode, useMemo } from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
ReactNode,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { MdDeviceHub, MdGridOn } from 'react-icons/md';
|
import { MdDeviceHub, MdGridOn } from 'react-icons/md';
|
||||||
import { activeTabIndexSelector } from '../store/uiSelectors';
|
import { activeTabIndexSelector } from '../store/uiSelectors';
|
||||||
@ -23,33 +32,47 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { ResourceKey } from 'i18next';
|
import { ResourceKey } from 'i18next';
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import NodeEditor from 'features/nodes/components/NodeEditor';
|
import NodeEditor from 'features/nodes/components/NodeEditor';
|
||||||
import GenerateWorkspace from './tabs/Generate/GenerateWorkspace';
|
import GenerateWorkspace from './tabs/Create/GenerateWorkspace';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { BsLightningChargeFill } from 'react-icons/bs';
|
import { BsLightningChargeFill } from 'react-icons/bs';
|
||||||
import { configSelector } from 'features/system/store/configSelectors';
|
import { configSelector } from 'features/system/store/configSelectors';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel';
|
||||||
|
import Scrollable from './common/Scrollable';
|
||||||
|
import GenerateParameters from './tabs/Create/GenerateParameters';
|
||||||
|
import PinParametersPanelButton from './PinParametersPanelButton';
|
||||||
|
import ParametersSlide from './common/ParametersSlide';
|
||||||
|
import ImageGalleryPanel from 'features/gallery/components/ImageGalleryPanel';
|
||||||
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
|
||||||
|
import CreateTabContent from './tabs/Create/GenerateContent';
|
||||||
|
import ParametersPanel from './ParametersPanel';
|
||||||
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
import CreateTab from './tabs/Create/CreateTab';
|
||||||
|
import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab';
|
||||||
|
import NodesTab from './tabs/Nodes/NodesTab';
|
||||||
|
|
||||||
export interface InvokeTabInfo {
|
export interface InvokeTabInfo {
|
||||||
id: InvokeTabName;
|
id: InvokeTabName;
|
||||||
icon: ReactNode;
|
icon: ReactNode;
|
||||||
workarea: ReactNode;
|
content: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs: InvokeTabInfo[] = [
|
const tabs: InvokeTabInfo[] = [
|
||||||
{
|
{
|
||||||
id: 'generate',
|
id: 'generate',
|
||||||
icon: <Icon as={BsLightningChargeFill} sx={{ boxSize: 5 }} />,
|
icon: <Icon as={BsLightningChargeFill} sx={{ boxSize: 5 }} />,
|
||||||
workarea: <GenerateWorkspace />,
|
content: <CreateTab />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'unifiedCanvas',
|
id: 'unifiedCanvas',
|
||||||
icon: <Icon as={MdGridOn} sx={{ boxSize: 6 }} />,
|
icon: <Icon as={MdGridOn} sx={{ boxSize: 6 }} />,
|
||||||
workarea: <UnifiedCanvasWorkarea />,
|
content: <UnifiedCanvasTab />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'nodes',
|
id: 'nodes',
|
||||||
icon: <Icon as={MdDeviceHub} sx={{ boxSize: 6 }} />,
|
icon: <Icon as={MdDeviceHub} sx={{ boxSize: 6 }} />,
|
||||||
workarea: <NodeEditor />,
|
content: <NodesTab />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -72,9 +95,12 @@ const InvokeTabs = () => {
|
|||||||
(state: RootState) => state.lightbox.isLightboxOpen
|
(state: RootState) => state.lightbox.isLightboxOpen
|
||||||
);
|
);
|
||||||
|
|
||||||
const { shouldPinGallery, shouldPinParametersPanel } = useAppSelector(
|
const {
|
||||||
(state: RootState) => state.ui
|
shouldPinGallery,
|
||||||
);
|
shouldPinParametersPanel,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
} = useAppSelector((state: RootState) => state.ui);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -133,9 +159,7 @@ const InvokeTabs = () => {
|
|||||||
|
|
||||||
const tabPanels = useMemo(
|
const tabPanels = useMemo(
|
||||||
() =>
|
() =>
|
||||||
enabledTabs.map((tab) => (
|
enabledTabs.map((tab) => <TabPanel key={tab.id}>{tab.content}</TabPanel>),
|
||||||
<TabPanel key={tab.id}>{tab.workarea}</TabPanel>
|
|
||||||
)),
|
|
||||||
[enabledTabs]
|
[enabledTabs]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -165,3 +189,17 @@ const InvokeTabs = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(InvokeTabs);
|
export default memo(InvokeTabs);
|
||||||
|
|
||||||
|
// <PanelGroup autoSaveId="example" direction="horizontal">
|
||||||
|
// <Panel defaultSize={25}>
|
||||||
|
// <SourcesExplorer />
|
||||||
|
// </Panel>
|
||||||
|
// <PanelResizeHandle />
|
||||||
|
// <Panel>
|
||||||
|
// <SourceViewer />
|
||||||
|
// </Panel>
|
||||||
|
// <PanelResizeHandle />
|
||||||
|
// <Panel defaultSize={25}>
|
||||||
|
// <Console />
|
||||||
|
// </Panel>
|
||||||
|
// </PanelGroup>;
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
import { PropsWithChildren, memo } from 'react';
|
||||||
|
|
||||||
|
const OverlayScrollable = (props: PropsWithChildren) => {
|
||||||
|
return (
|
||||||
|
<OverlayScrollbarsComponent
|
||||||
|
defer
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
options={{
|
||||||
|
scrollbars: {
|
||||||
|
visibility: 'auto',
|
||||||
|
autoHide: 'move',
|
||||||
|
autoHideDelay: 1300,
|
||||||
|
theme: 'os-theme-dark',
|
||||||
|
},
|
||||||
|
overflow: { x: 'hidden' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(OverlayScrollable);
|
@ -26,7 +26,6 @@ import {
|
|||||||
type ResizableDrawerProps = ResizableProps & {
|
type ResizableDrawerProps = ResizableProps & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
isResizable: boolean;
|
isResizable: boolean;
|
||||||
isPinned: boolean;
|
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
direction?: SlideDirection;
|
direction?: SlideDirection;
|
||||||
@ -51,7 +50,6 @@ const ChakraResizeable = chakra(Resizable, {
|
|||||||
const ResizableDrawer = ({
|
const ResizableDrawer = ({
|
||||||
direction = 'left',
|
direction = 'left',
|
||||||
isResizable,
|
isResizable,
|
||||||
isPinned,
|
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
children,
|
children,
|
||||||
@ -95,7 +93,7 @@ const ResizableDrawer = ({
|
|||||||
handler: () => {
|
handler: () => {
|
||||||
onClose();
|
onClose();
|
||||||
},
|
},
|
||||||
enabled: isOpen && !isPinned,
|
enabled: isOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleEnables = useMemo(
|
const handleEnables = useMemo(
|
||||||
@ -107,30 +105,33 @@ const ResizableDrawer = ({
|
|||||||
() =>
|
() =>
|
||||||
getMinMaxDimensions({
|
getMinMaxDimensions({
|
||||||
direction,
|
direction,
|
||||||
minWidth: isResizable
|
// minWidth: isResizable
|
||||||
? parseAndPadSize(minWidth, 18)
|
// ? parseAndPadSize(minWidth, 18)
|
||||||
: parseAndPadSize(minWidth),
|
// : parseAndPadSize(minWidth),
|
||||||
maxWidth: isResizable
|
// maxWidth: isResizable
|
||||||
? parseAndPadSize(maxWidth, 18)
|
// ? parseAndPadSize(maxWidth, 18)
|
||||||
: parseAndPadSize(maxWidth),
|
// : parseAndPadSize(maxWidth),
|
||||||
minHeight: isResizable
|
// minHeight: isResizable
|
||||||
? parseAndPadSize(minHeight, 18)
|
// ? parseAndPadSize(minHeight, 18)
|
||||||
: parseAndPadSize(minHeight),
|
// : parseAndPadSize(minHeight),
|
||||||
maxHeight: isResizable
|
// maxHeight: isResizable
|
||||||
? parseAndPadSize(maxHeight, 18)
|
// ? parseAndPadSize(maxHeight, 18)
|
||||||
: parseAndPadSize(maxHeight),
|
// : parseAndPadSize(maxHeight),
|
||||||
|
minWidth,
|
||||||
|
maxWidth,
|
||||||
|
minHeight,
|
||||||
|
maxHeight,
|
||||||
}),
|
}),
|
||||||
[minWidth, maxWidth, minHeight, maxHeight, direction, isResizable]
|
[minWidth, maxWidth, minHeight, maxHeight, direction]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { containerStyles, handleStyles } = useMemo(
|
const { containerStyles, handleStyles } = useMemo(
|
||||||
() =>
|
() =>
|
||||||
getStyles({
|
getStyles({
|
||||||
isPinned,
|
|
||||||
isResizable,
|
isResizable,
|
||||||
direction,
|
direction,
|
||||||
}),
|
}),
|
||||||
[isPinned, isResizable, direction]
|
[isResizable, direction]
|
||||||
);
|
);
|
||||||
|
|
||||||
const slideDirection = useMemo(
|
const slideDirection = useMemo(
|
||||||
@ -140,34 +141,37 @@ const ResizableDrawer = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (['left', 'right'].includes(direction)) {
|
if (['left', 'right'].includes(direction)) {
|
||||||
setHeight(isPinned ? '100%' : '100vh');
|
setHeight('100vh');
|
||||||
|
// setHeight(isPinned ? '100%' : '100vh');
|
||||||
}
|
}
|
||||||
if (['top', 'bottom'].includes(direction)) {
|
if (['top', 'bottom'].includes(direction)) {
|
||||||
setWidth(isPinned ? '100%' : '100vw');
|
setWidth('100vw');
|
||||||
|
// setWidth(isPinned ? '100%' : '100vw');
|
||||||
}
|
}
|
||||||
}, [isPinned, direction]);
|
}, [direction]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slide
|
<Slide
|
||||||
direction={slideDirection}
|
direction={slideDirection}
|
||||||
in={isOpen}
|
in={isOpen}
|
||||||
unmountOnExit={isPinned}
|
// unmountOnExit={isPinned}
|
||||||
motionProps={{ initial: isPinned }}
|
motionProps={{ initial: false }}
|
||||||
{...(isPinned
|
style={{ zIndex: 99, width: 'full' }}
|
||||||
? {
|
// {...(isPinned
|
||||||
style: {
|
// ? {
|
||||||
position: undefined,
|
// style: {
|
||||||
left: undefined,
|
// position: undefined,
|
||||||
right: undefined,
|
// left: undefined,
|
||||||
top: undefined,
|
// right: undefined,
|
||||||
bottom: undefined,
|
// top: undefined,
|
||||||
width: undefined,
|
// bottom: undefined,
|
||||||
},
|
// width: undefined,
|
||||||
}
|
// },
|
||||||
: {
|
// }
|
||||||
// transition: { enter: { duration: 0.15 }, exit: { duration: 0.15 } },
|
// : {
|
||||||
style: { zIndex: 99, width: 'full' },
|
// // transition: { enter: { duration: 0.15 }, exit: { duration: 0.15 } },
|
||||||
})}
|
// style: { zIndex: 99, width: 'full' },
|
||||||
|
// })}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
ref={outsideClickRef}
|
ref={outsideClickRef}
|
||||||
@ -186,10 +190,10 @@ const ResizableDrawer = ({
|
|||||||
{...minMaxDimensions}
|
{...minMaxDimensions}
|
||||||
sx={{
|
sx={{
|
||||||
borderColor: 'base.800',
|
borderColor: 'base.800',
|
||||||
p: isPinned ? 0 : 4,
|
p: 4,
|
||||||
bg: 'base.900',
|
bg: 'base.900',
|
||||||
height: 'full',
|
height: 'full',
|
||||||
boxShadow: !isPinned ? '0 0 4rem 0 rgba(0, 0, 0, 0.8)' : '',
|
boxShadow: '0 0 4rem 0 rgba(0, 0, 0, 0.8)',
|
||||||
...containerStyles,
|
...containerStyles,
|
||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
|
@ -155,7 +155,6 @@ export const getAnimations = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type GetResizableStylesProps = {
|
export type GetResizableStylesProps = {
|
||||||
isPinned: boolean;
|
|
||||||
isResizable: boolean;
|
isResizable: boolean;
|
||||||
direction: SlideDirection;
|
direction: SlideDirection;
|
||||||
};
|
};
|
||||||
@ -168,10 +167,10 @@ const HANDLE_PADDING = '1rem';
|
|||||||
|
|
||||||
const HANDLE_WIDTH_PINNED = '2px';
|
const HANDLE_WIDTH_PINNED = '2px';
|
||||||
const HANDLE_WIDTH_UNPINNED = '5px';
|
const HANDLE_WIDTH_UNPINNED = '5px';
|
||||||
|
const HANDLE_WIDTH = '5px';
|
||||||
|
|
||||||
// Get the styles for the container and handle. Do not need to handle langDirection here bc we use direction-agnostic CSS
|
// Get the styles for the container and handle. Do not need to handle langDirection here bc we use direction-agnostic CSS
|
||||||
export const getStyles = ({
|
export const getStyles = ({
|
||||||
isPinned,
|
|
||||||
isResizable,
|
isResizable,
|
||||||
direction,
|
direction,
|
||||||
}: GetResizableStylesProps): {
|
}: GetResizableStylesProps): {
|
||||||
@ -182,15 +181,13 @@ export const getStyles = ({
|
|||||||
return { containerStyles: {}, handleStyles: {} };
|
return { containerStyles: {}, handleStyles: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleWidth = isPinned ? HANDLE_WIDTH_PINNED : HANDLE_WIDTH_UNPINNED;
|
|
||||||
|
|
||||||
// Calculate the positioning offset of the handle hitbox so it is centered over the handle
|
// Calculate the positioning offset of the handle hitbox so it is centered over the handle
|
||||||
const handleOffset = `calc((2 * ${HANDLE_INTERACT_PADDING} + ${handleWidth}) / -2)`;
|
const handleOffset = `calc((2 * ${HANDLE_INTERACT_PADDING} + ${HANDLE_WIDTH}) / -2)`;
|
||||||
|
|
||||||
if (direction === 'top') {
|
if (direction === 'top') {
|
||||||
return {
|
return {
|
||||||
containerStyles: {
|
containerStyles: {
|
||||||
borderBottomWidth: handleWidth,
|
borderBottomWidth: HANDLE_WIDTH,
|
||||||
paddingBottom: HANDLE_PADDING,
|
paddingBottom: HANDLE_PADDING,
|
||||||
},
|
},
|
||||||
handleStyles: {
|
handleStyles: {
|
||||||
@ -206,7 +203,7 @@ export const getStyles = ({
|
|||||||
if (direction === 'left') {
|
if (direction === 'left') {
|
||||||
return {
|
return {
|
||||||
containerStyles: {
|
containerStyles: {
|
||||||
borderInlineEndWidth: handleWidth,
|
borderInlineEndWidth: HANDLE_WIDTH,
|
||||||
paddingInlineEnd: HANDLE_PADDING,
|
paddingInlineEnd: HANDLE_PADDING,
|
||||||
},
|
},
|
||||||
handleStyles: {
|
handleStyles: {
|
||||||
@ -222,7 +219,7 @@ export const getStyles = ({
|
|||||||
if (direction === 'bottom') {
|
if (direction === 'bottom') {
|
||||||
return {
|
return {
|
||||||
containerStyles: {
|
containerStyles: {
|
||||||
borderTopWidth: handleWidth,
|
borderTopWidth: HANDLE_WIDTH,
|
||||||
paddingTop: HANDLE_PADDING,
|
paddingTop: HANDLE_PADDING,
|
||||||
},
|
},
|
||||||
handleStyles: {
|
handleStyles: {
|
||||||
@ -238,7 +235,7 @@ export const getStyles = ({
|
|||||||
if (direction === 'right') {
|
if (direction === 'right') {
|
||||||
return {
|
return {
|
||||||
containerStyles: {
|
containerStyles: {
|
||||||
borderInlineStartWidth: handleWidth,
|
borderInlineStartWidth: HANDLE_WIDTH,
|
||||||
paddingInlineStart: HANDLE_PADDING,
|
paddingInlineStart: HANDLE_PADDING,
|
||||||
},
|
},
|
||||||
handleStyles: {
|
handleStyles: {
|
||||||
|
@ -0,0 +1,96 @@
|
|||||||
|
import { Portal, TabPanel } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
import GenerateParameters from './GenerateParameters';
|
||||||
|
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
||||||
|
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
|
import CreateTabContent from './GenerateContent';
|
||||||
|
import ResizeHandle from '../ResizeHandle';
|
||||||
|
import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel';
|
||||||
|
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
|
||||||
|
|
||||||
|
const selector = createSelector(uiSelector, (ui) => {
|
||||||
|
const {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
} = ui;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const CreateTab = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelGroup
|
||||||
|
direction="horizontal"
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
|
{shouldPinParametersPanel && shouldShowParametersPanel && (
|
||||||
|
<>
|
||||||
|
<Panel
|
||||||
|
order={0}
|
||||||
|
defaultSize={30}
|
||||||
|
minSize={20}
|
||||||
|
style={{ position: 'relative' }}
|
||||||
|
>
|
||||||
|
<GenerateParameters />
|
||||||
|
<PinParametersPanelButton
|
||||||
|
sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
<ResizeHandle />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{shouldPinParametersPanel && shouldShowParametersPanel && (
|
||||||
|
<>
|
||||||
|
<Panel
|
||||||
|
order={0}
|
||||||
|
defaultSize={30}
|
||||||
|
minSize={20}
|
||||||
|
style={{ position: 'relative' }}
|
||||||
|
>
|
||||||
|
<ImageToImageSettings />
|
||||||
|
</Panel>
|
||||||
|
<ResizeHandle />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Panel
|
||||||
|
order={1}
|
||||||
|
minSize={30}
|
||||||
|
onResize={() => {
|
||||||
|
dispatch(requestCanvasRescale());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CreateTabContent />
|
||||||
|
</Panel>
|
||||||
|
{shouldPinGallery && shouldShowGallery && (
|
||||||
|
<>
|
||||||
|
<ResizeHandle />
|
||||||
|
<Panel order={2} defaultSize={10} minSize={10}>
|
||||||
|
<ImageGalleryContent />
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PanelGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(CreateTab);
|
@ -2,7 +2,7 @@ import { Box, Flex } from '@chakra-ui/react';
|
|||||||
import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay';
|
import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay';
|
||||||
import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview';
|
import ProgressImagePreview from 'features/parameters/components/ProgressImagePreview';
|
||||||
|
|
||||||
const GenerateContent = () => {
|
const CreateTabContent = () => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -20,4 +20,4 @@ const GenerateContent = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GenerateContent;
|
export default CreateTabContent;
|
@ -30,9 +30,15 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces
|
|||||||
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
||||||
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
||||||
import { findIndex } from 'lodash-es';
|
import { findIndex } from 'lodash-es';
|
||||||
import { memo, useMemo, useState } from 'react';
|
import {
|
||||||
|
OverlayScrollbarsComponent,
|
||||||
|
useOverlayScrollbars,
|
||||||
|
} from 'overlayscrollbars-react';
|
||||||
|
import { memo, useMemo, useState, useRef, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||||
|
import OverlayScrollable from '../../common/OverlayScrollable';
|
||||||
|
import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel';
|
||||||
|
|
||||||
const GenerateParameters = () => {
|
const GenerateParameters = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -83,7 +89,16 @@ const GenerateParameters = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={2}>
|
<OverlayScrollable>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
gap: 2,
|
||||||
|
flexDirection: 'column',
|
||||||
|
h: 'full',
|
||||||
|
w: 'full',
|
||||||
|
position: 'absolute',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PromptInput />
|
<PromptInput />
|
||||||
<NegativePromptInput />
|
<NegativePromptInput />
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
@ -102,6 +117,7 @@ const GenerateParameters = () => {
|
|||||||
<ImageToImageToggle />
|
<ImageToImageToggle />
|
||||||
<ParametersAccordion accordionItems={generateAccordionItems} />
|
<ParametersAccordion accordionItems={generateAccordionItems} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</OverlayScrollable>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import GenerateContent from './GenerateContent';
|
import CreateTabContent from './GenerateContent';
|
||||||
import GenerateParameters from './GenerateParameters';
|
import GenerateParameters from './GenerateParameters';
|
||||||
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
@ -14,6 +14,8 @@ const GenerateWorkspace = () => {
|
|||||||
(state: RootState) => state.ui.shouldPinParametersPanel
|
(state: RootState) => state.ui.shouldPinParametersPanel
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return <CreateTabContent />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection={{ base: 'column-reverse', xl: 'row' }}
|
flexDirection={{ base: 'column-reverse', xl: 'row' }}
|
||||||
@ -45,7 +47,7 @@ const GenerateWorkspace = () => {
|
|||||||
<GenerateParameters />
|
<GenerateParameters />
|
||||||
</ParametersSlide>
|
</ParametersSlide>
|
||||||
)}
|
)}
|
||||||
<GenerateContent />
|
<CreateTabContent />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -0,0 +1,64 @@
|
|||||||
|
import { TabPanel } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
||||||
|
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
|
import ResizeHandle from '../ResizeHandle';
|
||||||
|
import NodeEditor from 'features/nodes/components/NodeEditor';
|
||||||
|
|
||||||
|
const selector = createSelector(uiSelector, (ui) => {
|
||||||
|
const {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
} = ui;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const NodesTab = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelGroup
|
||||||
|
direction="horizontal"
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
|
<Panel
|
||||||
|
order={1}
|
||||||
|
minSize={30}
|
||||||
|
onResize={() => {
|
||||||
|
dispatch(requestCanvasRescale());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<NodeEditor />
|
||||||
|
</Panel>
|
||||||
|
{shouldPinGallery && shouldShowGallery && (
|
||||||
|
<>
|
||||||
|
<ResizeHandle />
|
||||||
|
<Panel order={2} defaultSize={10} minSize={10} collapsible={true}>
|
||||||
|
<ImageGalleryContent />
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PanelGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(NodesTab);
|
@ -0,0 +1,17 @@
|
|||||||
|
import { Box, Flex } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
|
const ResizeHandle = () => {
|
||||||
|
return (
|
||||||
|
<PanelResizeHandle>
|
||||||
|
<Flex
|
||||||
|
sx={{ w: 6, h: 'full', justifyContent: 'center', alignItems: 'center' }}
|
||||||
|
>
|
||||||
|
<Box sx={{ w: 0.5, h: 'calc(100% - 1rem)', py: 4, bg: 'base.800' }} />
|
||||||
|
</Flex>
|
||||||
|
</PanelResizeHandle>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(ResizeHandle);
|
@ -17,6 +17,7 @@ import ProcessButtons from 'features/parameters/components/ProcessButtons/Proces
|
|||||||
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
||||||
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import OverlayScrollable from '../../common/OverlayScrollable';
|
||||||
|
|
||||||
export default function UnifiedCanvasParameters() {
|
export default function UnifiedCanvasParameters() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -74,11 +75,21 @@ export default function UnifiedCanvasParameters() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDir="column" gap={2} position="relative">
|
<OverlayScrollable>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
gap: 2,
|
||||||
|
flexDirection: 'column',
|
||||||
|
h: 'full',
|
||||||
|
w: 'full',
|
||||||
|
position: 'absolute',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PromptInput />
|
<PromptInput />
|
||||||
<NegativePromptInput />
|
<NegativePromptInput />
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<ParametersAccordion accordionItems={unifiedCanvasAccordions} />
|
<ParametersAccordion accordionItems={unifiedCanvasAccordions} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</OverlayScrollable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,89 @@
|
|||||||
|
import { TabPanel } from '@chakra-ui/react';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
||||||
|
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
|
import UnifiedCanvasContent from './UnifiedCanvasContent';
|
||||||
|
import ResizeHandle from '../ResizeHandle';
|
||||||
|
import UnifiedCanvasParameters from './UnifiedCanvasParameters';
|
||||||
|
import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
|
||||||
|
|
||||||
|
const selector = createSelector(uiSelector, (ui) => {
|
||||||
|
const {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
shouldUseCanvasBetaLayout,
|
||||||
|
} = ui;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
shouldUseCanvasBetaLayout,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const UnifiedCanvasTab = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
shouldPinGallery,
|
||||||
|
shouldShowGallery,
|
||||||
|
shouldPinParametersPanel,
|
||||||
|
shouldShowParametersPanel,
|
||||||
|
shouldUseCanvasBetaLayout,
|
||||||
|
} = useAppSelector(selector);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelGroup
|
||||||
|
direction="horizontal"
|
||||||
|
style={{ height: '100%', width: '100%' }}
|
||||||
|
>
|
||||||
|
{shouldPinParametersPanel && shouldShowParametersPanel && (
|
||||||
|
<>
|
||||||
|
<Panel
|
||||||
|
order={0}
|
||||||
|
defaultSize={30}
|
||||||
|
minSize={20}
|
||||||
|
style={{ position: 'relative' }}
|
||||||
|
>
|
||||||
|
<UnifiedCanvasParameters />
|
||||||
|
<PinParametersPanelButton
|
||||||
|
sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}
|
||||||
|
/>
|
||||||
|
</Panel>
|
||||||
|
<ResizeHandle />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Panel
|
||||||
|
order={1}
|
||||||
|
minSize={30}
|
||||||
|
onResize={() => {
|
||||||
|
dispatch(requestCanvasRescale());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{shouldUseCanvasBetaLayout ? (
|
||||||
|
<UnifiedCanvasContentBeta />
|
||||||
|
) : (
|
||||||
|
<UnifiedCanvasContent />
|
||||||
|
)}
|
||||||
|
</Panel>
|
||||||
|
{shouldPinGallery && shouldShowGallery && (
|
||||||
|
<>
|
||||||
|
<ResizeHandle />
|
||||||
|
<Panel order={2} defaultSize={10} minSize={10}>
|
||||||
|
<ImageGalleryContent />
|
||||||
|
</Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PanelGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(UnifiedCanvasTab);
|
@ -18,6 +18,12 @@ const CanvasWorkspace = () => {
|
|||||||
(state: RootState) => state.ui.shouldUseCanvasBetaLayout
|
(state: RootState) => state.ui.shouldUseCanvasBetaLayout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return shouldUseCanvasBetaLayout ? (
|
||||||
|
<UnifiedCanvasContentBeta />
|
||||||
|
) : (
|
||||||
|
<UnifiedCanvasContent />
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection={{ base: 'column-reverse', xl: 'row' }}
|
flexDirection={{ base: 'column-reverse', xl: 'row' }}
|
||||||
@ -25,7 +31,7 @@ const CanvasWorkspace = () => {
|
|||||||
h="full"
|
h="full"
|
||||||
gap={4}
|
gap={4}
|
||||||
>
|
>
|
||||||
{shouldPinParametersPanel ? (
|
{/* {shouldPinParametersPanel ? (
|
||||||
<Box width="28rem" flexShrink={0} position="relative">
|
<Box width="28rem" flexShrink={0} position="relative">
|
||||||
<Scrollable>
|
<Scrollable>
|
||||||
<UnifiedCanvasParameters />
|
<UnifiedCanvasParameters />
|
||||||
@ -38,7 +44,7 @@ const CanvasWorkspace = () => {
|
|||||||
<ParametersSlide>
|
<ParametersSlide>
|
||||||
<UnifiedCanvasParameters />
|
<UnifiedCanvasParameters />
|
||||||
</ParametersSlide>
|
</ParametersSlide>
|
||||||
)}
|
)} */}
|
||||||
{shouldUseCanvasBetaLayout ? (
|
{shouldUseCanvasBetaLayout ? (
|
||||||
<UnifiedCanvasContentBeta />
|
<UnifiedCanvasContentBeta />
|
||||||
) : (
|
) : (
|
||||||
|
@ -78,9 +78,15 @@ export const uiSlice = createSlice({
|
|||||||
},
|
},
|
||||||
togglePinGalleryPanel: (state) => {
|
togglePinGalleryPanel: (state) => {
|
||||||
state.shouldPinGallery = !state.shouldPinGallery;
|
state.shouldPinGallery = !state.shouldPinGallery;
|
||||||
|
if (!state.shouldPinGallery) {
|
||||||
|
state.shouldShowGallery = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
togglePinParametersPanel: (state) => {
|
togglePinParametersPanel: (state) => {
|
||||||
state.shouldPinParametersPanel = !state.shouldPinParametersPanel;
|
state.shouldPinParametersPanel = !state.shouldPinParametersPanel;
|
||||||
|
if (!state.shouldPinParametersPanel) {
|
||||||
|
state.shouldShowParametersPanel = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggleParametersPanel: (state) => {
|
toggleParametersPanel: (state) => {
|
||||||
state.shouldShowParametersPanel = !state.shouldShowParametersPanel;
|
state.shouldShowParametersPanel = !state.shouldShowParametersPanel;
|
||||||
|
@ -5579,6 +5579,11 @@ react-remove-scroll@^2.5.5:
|
|||||||
use-callback-ref "^1.3.0"
|
use-callback-ref "^1.3.0"
|
||||||
use-sidecar "^1.1.2"
|
use-sidecar "^1.1.2"
|
||||||
|
|
||||||
|
react-resizable-panels@^0.0.42:
|
||||||
|
version "0.0.42"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-resizable-panels/-/react-resizable-panels-0.0.42.tgz#e1a5d7fde7be4d18f32d0e021a0b4edb28b9edfe"
|
||||||
|
integrity sha512-nOaN9DeUTsmKjN3MFGaLd35kngGyO29SHRLdBRafYR1SV2F/LbWbpVUKVPwL2fBBTnQe2/rqOQwT4k+3cKeK+w==
|
||||||
|
|
||||||
react-rnd@^10.4.1:
|
react-rnd@^10.4.1:
|
||||||
version "10.4.1"
|
version "10.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.4.1.tgz#9e1c3f244895d7862ef03be98b2a620848c3fba1"
|
resolved "https://registry.yarnpkg.com/react-rnd/-/react-rnd-10.4.1.tgz#9e1c3f244895d7862ef03be98b2a620848c3fba1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user