mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Adds pin feature to options panel
This commit is contained in:
parent
dc556cb1a7
commit
e58b7a7ef9
517
frontend/dist/assets/index.2d90b6f0.js
vendored
Normal file
517
frontend/dist/assets/index.2d90b6f0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.352e4760.css
vendored
1
frontend/dist/assets/index.352e4760.css
vendored
File diff suppressed because one or more lines are too long
517
frontend/dist/assets/index.64b87783.js
vendored
517
frontend/dist/assets/index.64b87783.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/assets/index.8c67e6b9.css
vendored
Normal file
1
frontend/dist/assets/index.8c67e6b9.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -6,8 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
<title>InvokeAI - A Stable Diffusion Toolkit</title>
|
||||||
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
<link rel="shortcut icon" type="icon" href="./assets/favicon.0d253ced.ico" />
|
||||||
<script type="module" crossorigin src="./assets/index.64b87783.js"></script>
|
<script type="module" crossorigin src="./assets/index.2d90b6f0.js"></script>
|
||||||
<link rel="stylesheet" href="./assets/index.352e4760.css">
|
<link rel="stylesheet" href="./assets/index.8c67e6b9.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -11,11 +11,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
row-gap: 0.5rem;
|
row-gap: 0.5rem;
|
||||||
padding: $app-padding;
|
padding: $app-padding;
|
||||||
grid-auto-rows: max-content;
|
grid-auto-rows: min-content auto;
|
||||||
width: $app-width;
|
width: $app-width;
|
||||||
height: $app-height;
|
height: $app-height;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-console {
|
|
||||||
z-index: 20;
|
|
||||||
}
|
|
||||||
|
@ -6,16 +6,52 @@ import Loading from '../Loading';
|
|||||||
import { useAppDispatch } from './store';
|
import { useAppDispatch } from './store';
|
||||||
import { requestSystemConfig } from './socketio/actions';
|
import { requestSystemConfig } from './socketio/actions';
|
||||||
import { keepGUIAlive } from './utils';
|
import { keepGUIAlive } from './utils';
|
||||||
import InvokeTabs from '../features/tabs/InvokeTabs';
|
import InvokeTabs, { tabMap } from '../features/tabs/InvokeTabs';
|
||||||
import ImageUploader from '../common/components/ImageUploader';
|
import ImageUploader from '../common/components/ImageUploader';
|
||||||
|
import { RootState, useAppSelector } from '../app/store';
|
||||||
|
|
||||||
|
import ShowHideGalleryButton from '../features/tabs/ShowHideGalleryButton';
|
||||||
|
import ShowHideOptionsPanelButton from '../features/tabs/ShowHideOptionsPanelButton';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { GalleryState } from '../features/gallery/gallerySlice';
|
||||||
|
import { OptionsState } from '../features/options/optionsSlice';
|
||||||
|
|
||||||
keepGUIAlive();
|
keepGUIAlive();
|
||||||
|
|
||||||
|
const appSelector = createSelector(
|
||||||
|
[(state: RootState) => state.gallery, (state: RootState) => state.options],
|
||||||
|
(gallery: GalleryState, options: OptionsState) => {
|
||||||
|
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
||||||
|
gallery;
|
||||||
|
const {
|
||||||
|
shouldShowOptionsPanel,
|
||||||
|
shouldHoldOptionsPanelOpen,
|
||||||
|
shouldPinOptionsPanel,
|
||||||
|
activeTab,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldShowGalleryButton: !(
|
||||||
|
shouldShowGallery ||
|
||||||
|
(shouldHoldGalleryOpen && !shouldPinGallery)
|
||||||
|
),
|
||||||
|
shouldShowOptionsPanelButton:
|
||||||
|
!(
|
||||||
|
shouldShowOptionsPanel ||
|
||||||
|
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
||||||
|
) && ['txt2img', 'img2img', 'inpainting'].includes(tabMap[activeTab]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [isReady, setIsReady] = useState<boolean>(false);
|
const [isReady, setIsReady] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const { shouldShowGalleryButton, shouldShowOptionsPanelButton } =
|
||||||
|
useAppSelector(appSelector);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(requestSystemConfig());
|
dispatch(requestSystemConfig());
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
@ -32,6 +68,8 @@ const App = () => {
|
|||||||
<div className="app-console">
|
<div className="app-console">
|
||||||
<Console />
|
<Console />
|
||||||
</div>
|
</div>
|
||||||
|
{shouldShowGalleryButton && <ShowHideGalleryButton />}
|
||||||
|
{shouldShowOptionsPanelButton && <ShowHideOptionsPanelButton />}
|
||||||
</ImageUploader>
|
</ImageUploader>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { FormControl, FormLabel, Select, SelectProps } from '@chakra-ui/react';
|
import { FormControl, FormLabel, Select, SelectProps } from '@chakra-ui/react';
|
||||||
|
import { MouseEvent } from 'react';
|
||||||
|
|
||||||
interface Props extends SelectProps {
|
interface Props extends SelectProps {
|
||||||
label: string;
|
label: string;
|
||||||
@ -21,7 +22,16 @@ const IAISelect = (props: Props) => {
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
return (
|
||||||
<FormControl isDisabled={isDisabled} className={`invokeai__select ${styleClass}`}>
|
<FormControl
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
className={`invokeai__select ${styleClass}`}
|
||||||
|
onClick={(e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
e.nativeEvent.stopPropagation();
|
||||||
|
e.nativeEvent.cancelBubble = true;
|
||||||
|
}}
|
||||||
|
>
|
||||||
<FormLabel
|
<FormLabel
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
marginBottom={1}
|
marginBottom={1}
|
||||||
|
@ -142,10 +142,10 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu.Root
|
<ContextMenu.Root
|
||||||
onOpenChange={(open: boolean) => {
|
// onOpenChange={(open: boolean) => {
|
||||||
dispatch(setShouldHoldGalleryOpen(open));
|
// dispatch(setShouldHoldGalleryOpen(open));
|
||||||
dispatch(setShouldShowGallery(true));
|
// dispatch(setShouldShowGallery(true));
|
||||||
}}
|
// }}
|
||||||
>
|
>
|
||||||
<ContextMenu.Trigger>
|
<ContextMenu.Trigger>
|
||||||
<Box
|
<Box
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
@use '../../styles/Mixins/' as *;
|
@use '../../styles/Mixins/' as *;
|
||||||
|
|
||||||
.image-gallery-area-enter {
|
.image-gallery-wrapper-enter {
|
||||||
transform: translateX(150%);
|
transform: translateX(150%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-gallery-area-enter-active {
|
.image-gallery-wrapper-enter-active {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
transition: all 120ms ease-out;
|
transition: all 120ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-gallery-area-exit {
|
.image-gallery-wrapper-exit {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-gallery-area-exit-active {
|
.image-gallery-wrapper-exit-active {
|
||||||
transform: translateX(150%);
|
transform: translateX(150%);
|
||||||
transition: all 120ms ease-out;
|
transition: all 120ms ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-gallery-area {
|
.image-gallery-wrapper {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
&[data-pinned='false'] {
|
&[data-pinned='false'] {
|
||||||
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
.image-gallery-popup {
|
.image-gallery-popup {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
box-shadow: 0 0 1rem var(--text-color-a3);
|
||||||
.image-gallery-container {
|
.image-gallery-container {
|
||||||
max-height: calc($app-height + 5rem);
|
max-height: calc($app-height + 5rem);
|
||||||
}
|
}
|
||||||
|
@ -96,8 +96,8 @@ export default function ImageGallery() {
|
|||||||
const timeoutIdRef = useRef<number | null>(null);
|
const timeoutIdRef = useRef<number | null>(null);
|
||||||
|
|
||||||
const handleSetShouldPinGallery = () => {
|
const handleSetShouldPinGallery = () => {
|
||||||
dispatch(setNeedsCache(true));
|
|
||||||
dispatch(setShouldPinGallery(!shouldPinGallery));
|
dispatch(setShouldPinGallery(!shouldPinGallery));
|
||||||
|
dispatch(setNeedsCache(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggleGallery = () => {
|
const handleToggleGallery = () => {
|
||||||
@ -106,7 +106,7 @@ export default function ImageGallery() {
|
|||||||
|
|
||||||
const handleOpenGallery = () => {
|
const handleOpenGallery = () => {
|
||||||
dispatch(setShouldShowGallery(true));
|
dispatch(setShouldShowGallery(true));
|
||||||
dispatch(setNeedsCache(true));
|
shouldPinGallery && dispatch(setNeedsCache(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseGallery = () => {
|
const handleCloseGallery = () => {
|
||||||
@ -117,7 +117,7 @@ export default function ImageGallery() {
|
|||||||
);
|
);
|
||||||
dispatch(setShouldShowGallery(false));
|
dispatch(setShouldShowGallery(false));
|
||||||
dispatch(setShouldHoldGalleryOpen(false));
|
dispatch(setShouldHoldGalleryOpen(false));
|
||||||
dispatch(setNeedsCache(true));
|
shouldPinGallery && dispatch(setNeedsCache(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickLoadMore = () => {
|
const handleClickLoadMore = () => {
|
||||||
@ -251,16 +251,20 @@ export default function ImageGallery() {
|
|||||||
galleryContainerRef.current.scrollTop = galleryScrollPosition;
|
galleryContainerRef.current.scrollTop = galleryScrollPosition;
|
||||||
}, [galleryScrollPosition, shouldShowGallery]);
|
}, [galleryScrollPosition, shouldShowGallery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setShouldShowButtons(galleryWidth >= 280);
|
||||||
|
}, [galleryWidth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
nodeRef={galleryRef}
|
nodeRef={galleryRef}
|
||||||
in={shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)}
|
in={shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)}
|
||||||
unmountOnExit
|
unmountOnExit
|
||||||
timeout={200}
|
timeout={200}
|
||||||
classNames="image-gallery-area"
|
classNames="image-gallery-wrapper"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="image-gallery-area"
|
className="image-gallery-wrapper"
|
||||||
data-pinned={shouldPinGallery}
|
data-pinned={shouldPinGallery}
|
||||||
ref={galleryRef}
|
ref={galleryRef}
|
||||||
onMouseLeave={!shouldPinGallery ? setCloseGalleryTimer : undefined}
|
onMouseLeave={!shouldPinGallery ? setCloseGalleryTimer : undefined}
|
||||||
@ -270,7 +274,6 @@ export default function ImageGallery() {
|
|||||||
<Resizable
|
<Resizable
|
||||||
minWidth={galleryMinWidth}
|
minWidth={galleryMinWidth}
|
||||||
maxWidth={galleryMaxWidth}
|
maxWidth={galleryMaxWidth}
|
||||||
// maxHeight={'100%'}
|
|
||||||
className={'image-gallery-popup'}
|
className={'image-gallery-popup'}
|
||||||
handleStyles={{ left: { width: '15px' } }}
|
handleStyles={{ left: { width: '15px' } }}
|
||||||
enable={{
|
enable={{
|
||||||
@ -316,9 +319,9 @@ export default function ImageGallery() {
|
|||||||
Number(galleryMaxWidth)
|
Number(galleryMaxWidth)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (newWidth >= 320 && !shouldShowButtons) {
|
if (newWidth >= 280 && !shouldShowButtons) {
|
||||||
setShouldShowButtons(true);
|
setShouldShowButtons(true);
|
||||||
} else if (newWidth < 320 && shouldShowButtons) {
|
} else if (newWidth < 280 && shouldShowButtons) {
|
||||||
setShouldShowButtons(false);
|
setShouldShowButtons(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,15 +451,6 @@ export default function ImageGallery() {
|
|||||||
icon={<BsPinAngleFill />}
|
icon={<BsPinAngleFill />}
|
||||||
data-selected={shouldPinGallery}
|
data-selected={shouldPinGallery}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IAIIconButton
|
|
||||||
size={'sm'}
|
|
||||||
aria-label={'Close Gallery'}
|
|
||||||
tooltip={'Close Gallery (G)'}
|
|
||||||
onClick={handleCloseGallery}
|
|
||||||
className="image-gallery-icon-btn"
|
|
||||||
icon={<MdClear />}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="image-gallery-container" ref={galleryContainerRef}>
|
<div className="image-gallery-container" ref={galleryContainerRef}>
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
@use '../../styles/Mixins/' as *;
|
|
||||||
|
|
||||||
.show-hide-gallery-button {
|
|
||||||
position: absolute !important;
|
|
||||||
top: 50%;
|
|
||||||
right: -1rem;
|
|
||||||
transform: translate(0, -50%);
|
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
border-radius: 0.5rem 0 0 0.5rem !important;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
|
|
||||||
@include Button(
|
|
||||||
$btn-width: 1rem,
|
|
||||||
$btn-height: 12rem,
|
|
||||||
$icon-size: 20px,
|
|
||||||
$btn-color: var(--btn-grey),
|
|
||||||
$btn-color-hover: var(--btn-grey-hover)
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
|
||||||
import { MdPhotoLibrary } from 'react-icons/md';
|
|
||||||
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
|
||||||
import IAIIconButton from '../../common/components/IAIIconButton';
|
|
||||||
import { setShouldShowGallery } from '../gallery/gallerySlice';
|
|
||||||
import { selectNextImage, selectPrevImage } from './gallerySlice';
|
|
||||||
|
|
||||||
const ShowHideGalleryButton = () => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const { shouldPinGallery, shouldShowGallery } = useAppSelector(
|
|
||||||
(state: RootState) => state.gallery
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleShowGalleryToggle = () => {
|
|
||||||
dispatch(setShouldShowGallery(!shouldShowGallery));
|
|
||||||
};
|
|
||||||
|
|
||||||
// useHotkeys(
|
|
||||||
// 'g',
|
|
||||||
// () => {
|
|
||||||
// handleShowGalleryToggle();
|
|
||||||
// },
|
|
||||||
// [shouldShowGallery]
|
|
||||||
// );
|
|
||||||
|
|
||||||
// useHotkeys(
|
|
||||||
// 'left',
|
|
||||||
// () => {
|
|
||||||
// dispatch(selectPrevImage());
|
|
||||||
// },
|
|
||||||
// []
|
|
||||||
// );
|
|
||||||
|
|
||||||
// useHotkeys(
|
|
||||||
// 'right',
|
|
||||||
// () => {
|
|
||||||
// dispatch(selectNextImage());
|
|
||||||
// },
|
|
||||||
// []
|
|
||||||
// );
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIIconButton
|
|
||||||
tooltip="Show Gallery (G)"
|
|
||||||
tooltipPlacement="top"
|
|
||||||
aria-label="Show Gallery"
|
|
||||||
onClick={handleShowGalleryToggle}
|
|
||||||
styleClass="show-hide-gallery-button"
|
|
||||||
onMouseOver={!shouldPinGallery ? handleShowGalleryToggle : undefined}
|
|
||||||
>
|
|
||||||
<MdPhotoLibrary />
|
|
||||||
</IAIIconButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ShowHideGalleryButton;
|
|
@ -42,6 +42,10 @@ export interface OptionsState {
|
|||||||
activeTab: number;
|
activeTab: number;
|
||||||
shouldShowImageDetails: boolean;
|
shouldShowImageDetails: boolean;
|
||||||
showDualDisplay: boolean;
|
showDualDisplay: boolean;
|
||||||
|
shouldShowOptionsPanel: boolean;
|
||||||
|
shouldPinOptionsPanel: boolean;
|
||||||
|
optionsPanelScrollPosition: number;
|
||||||
|
shouldHoldOptionsPanelOpen: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialOptionsState: OptionsState = {
|
const initialOptionsState: OptionsState = {
|
||||||
@ -75,6 +79,10 @@ const initialOptionsState: OptionsState = {
|
|||||||
activeTab: 0,
|
activeTab: 0,
|
||||||
shouldShowImageDetails: false,
|
shouldShowImageDetails: false,
|
||||||
showDualDisplay: true,
|
showDualDisplay: true,
|
||||||
|
shouldShowOptionsPanel: true,
|
||||||
|
shouldPinOptionsPanel: true,
|
||||||
|
optionsPanelScrollPosition: 0,
|
||||||
|
shouldHoldOptionsPanelOpen: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: OptionsState = initialOptionsState;
|
const initialState: OptionsState = initialOptionsState;
|
||||||
@ -324,6 +332,18 @@ export const optionsSlice = createSlice({
|
|||||||
clearInitialImage: (state) => {
|
clearInitialImage: (state) => {
|
||||||
state.initialImage = undefined;
|
state.initialImage = undefined;
|
||||||
},
|
},
|
||||||
|
setShouldPinOptionsPanel: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.shouldPinOptionsPanel = action.payload;
|
||||||
|
},
|
||||||
|
setShouldShowOptionsPanel: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.shouldShowOptionsPanel = action.payload;
|
||||||
|
},
|
||||||
|
setOptionsPanelScrollPosition: (state, action: PayloadAction<number>) => {
|
||||||
|
state.optionsPanelScrollPosition = action.payload;
|
||||||
|
},
|
||||||
|
setShouldHoldOptionsPanelOpen: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.shouldHoldOptionsPanelOpen = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -366,6 +386,10 @@ export const {
|
|||||||
setShowDualDisplay,
|
setShowDualDisplay,
|
||||||
setInitialImage,
|
setInitialImage,
|
||||||
clearInitialImage,
|
clearInitialImage,
|
||||||
|
setShouldShowOptionsPanel,
|
||||||
|
setShouldPinOptionsPanel,
|
||||||
|
setOptionsPanelScrollPosition,
|
||||||
|
setShouldHoldOptionsPanelOpen,
|
||||||
} = optionsSlice.actions;
|
} = optionsSlice.actions;
|
||||||
|
|
||||||
export default optionsSlice.reducer;
|
export default optionsSlice.reducer;
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
.console-resizable {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.console {
|
.console {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -41,12 +48,13 @@
|
|||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
bottom: 0.5rem;
|
bottom: 0.5rem;
|
||||||
|
z-index: 21;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--console-icon-button-bg-color-hover) !important;
|
background: var(--console-icon-button-bg-color-hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.error-seen {
|
&[data-error-seen='true'] {
|
||||||
background: var(--status-bad-color) !important;
|
background: var(--status-bad-color) !important;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--status-bad-color) !important;
|
background: var(--status-bad-color) !important;
|
||||||
@ -59,12 +67,13 @@
|
|||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
left: 0.5rem;
|
left: 0.5rem;
|
||||||
bottom: 3rem;
|
bottom: 3rem;
|
||||||
|
z-index: 21;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--console-icon-button-bg-color-hover) !important;
|
background: var(--console-icon-button-bg-color-hover) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.autoscroll-enabled {
|
&[data-autoscroll-enabled='true'] {
|
||||||
background: var(--accent-color) !important;
|
background: var(--accent-color) !important;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--accent-color-hover) !important;
|
background: var(--accent-color-hover) !important;
|
||||||
|
@ -2,7 +2,7 @@ import { IconButton, Tooltip } from '@chakra-ui/react';
|
|||||||
import { useAppDispatch, useAppSelector } from '../../app/store';
|
import { useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
import { RootState } from '../../app/store';
|
import { RootState } from '../../app/store';
|
||||||
import { errorSeen, setShouldShowLogViewer, SystemState } from './systemSlice';
|
import { errorSeen, setShouldShowLogViewer, SystemState } from './systemSlice';
|
||||||
import { useLayoutEffect, useRef, useState } from 'react';
|
import { UIEvent, useLayoutEffect, useRef, useState } from 'react';
|
||||||
import { FaAngleDoubleDown, FaCode, FaMinus } from 'react-icons/fa';
|
import { FaAngleDoubleDown, FaCode, FaMinus } from 'react-icons/fa';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
@ -75,6 +75,17 @@ const Console = () => {
|
|||||||
[shouldShowLogViewer]
|
[shouldShowLogViewer]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleOnScroll = () => {
|
||||||
|
if (!viewerRef.current) return;
|
||||||
|
if (
|
||||||
|
shouldAutoscroll &&
|
||||||
|
viewerRef.current.scrollTop <
|
||||||
|
viewerRef.current.scrollHeight - viewerRef.current.clientHeight
|
||||||
|
) {
|
||||||
|
setShouldAutoscroll(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{shouldShowLogViewer && (
|
{shouldShowLogViewer && (
|
||||||
@ -83,10 +94,16 @@ const Console = () => {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
height: 200,
|
height: 200,
|
||||||
}}
|
}}
|
||||||
style={{ display: 'flex', position: 'fixed', left: 0, bottom: 0 }}
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
position: 'fixed',
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
zIndex: 20,
|
||||||
|
}}
|
||||||
maxHeight={'90vh'}
|
maxHeight={'90vh'}
|
||||||
>
|
>
|
||||||
<div className="console" ref={viewerRef}>
|
<div className="console" ref={viewerRef} onScroll={handleOnScroll}>
|
||||||
{log.map((entry, i) => {
|
{log.map((entry, i) => {
|
||||||
const { timestamp, message, level } = entry;
|
const { timestamp, message, level } = entry;
|
||||||
return (
|
return (
|
||||||
@ -100,11 +117,13 @@ const Console = () => {
|
|||||||
</Resizable>
|
</Resizable>
|
||||||
)}
|
)}
|
||||||
{shouldShowLogViewer && (
|
{shouldShowLogViewer && (
|
||||||
<Tooltip hasArrow label={shouldAutoscroll ? 'Autoscroll On' : 'Autoscroll Off'}>
|
<Tooltip
|
||||||
|
hasArrow
|
||||||
|
label={shouldAutoscroll ? 'Autoscroll On' : 'Autoscroll Off'}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={`console-autoscroll-icon-button ${
|
className={'console-autoscroll-icon-button'}
|
||||||
shouldAutoscroll && 'autoscroll-enabled'
|
data-autoscroll-enabled={shouldAutoscroll}
|
||||||
}`}
|
|
||||||
size="sm"
|
size="sm"
|
||||||
aria-label="Toggle autoscroll"
|
aria-label="Toggle autoscroll"
|
||||||
variant={'solid'}
|
variant={'solid'}
|
||||||
@ -113,16 +132,17 @@ const Console = () => {
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip hasArrow label={shouldShowLogViewer ? 'Hide Console' : 'Show Console'}>
|
<Tooltip
|
||||||
|
hasArrow
|
||||||
|
label={shouldShowLogViewer ? 'Hide Console' : 'Show Console'}
|
||||||
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
className={`console-toggle-icon-button ${
|
className={'console-toggle-icon-button'}
|
||||||
(hasError || !wasErrorSeen) && 'error-seen'
|
data-error-seen={hasError || !wasErrorSeen}
|
||||||
}`}
|
|
||||||
size="sm"
|
size="sm"
|
||||||
position={'fixed'}
|
position={'fixed'}
|
||||||
variant={'solid'}
|
variant={'solid'}
|
||||||
aria-label="Toggle Log Viewer"
|
aria-label="Toggle Log Viewer"
|
||||||
// colorScheme={hasError || !wasErrorSeen ? 'red' : 'gray'}
|
|
||||||
icon={shouldShowLogViewer ? <FaMinus /> : <FaCode />}
|
icon={shouldShowLogViewer ? <FaMinus /> : <FaCode />}
|
||||||
onClick={handleClickLogViewerToggle}
|
onClick={handleClickLogViewerToggle}
|
||||||
/>
|
/>
|
||||||
|
30
frontend/src/features/tabs/FloatingShowHideButton.scss
Normal file
30
frontend/src/features/tabs/FloatingShowHideButton.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
@use '../../styles/Mixins/' as *;
|
||||||
|
|
||||||
|
.floating-show-hide-button {
|
||||||
|
position: absolute !important;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
z-index: 20;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 2rem !important;
|
||||||
|
|
||||||
|
filter: drop-shadow(0 0 1rem var(--text-color-a3));
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0 0.5rem 0.5rem 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0.5rem 0 0 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include Button(
|
||||||
|
$btn-width: 1rem,
|
||||||
|
$btn-height: 12rem,
|
||||||
|
$icon-size: 20px,
|
||||||
|
$btn-color: var(--btn-grey),
|
||||||
|
$btn-color-hover: var(--btn-grey-hover)
|
||||||
|
);
|
||||||
|
}
|
@ -8,15 +8,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-to-image-panel {
|
|
||||||
display: grid;
|
|
||||||
row-gap: 1rem;
|
|
||||||
grid-auto-rows: max-content;
|
|
||||||
height: $app-content-height;
|
|
||||||
overflow-y: scroll;
|
|
||||||
@include HideScrollbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
.image-to-image-strength-main-option {
|
.image-to-image-strength-main-option {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: none !important;
|
grid-template-columns: none !important;
|
||||||
|
@ -17,6 +17,7 @@ import MainOptions from '../../options/MainOptions/MainOptions';
|
|||||||
import OptionsAccordion from '../../options/OptionsAccordion';
|
import OptionsAccordion from '../../options/OptionsAccordion';
|
||||||
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
||||||
import PromptInput from '../../options/PromptInput/PromptInput';
|
import PromptInput from '../../options/PromptInput/PromptInput';
|
||||||
|
import InvokeOptionsPanel from '../InvokeOptionsPanel';
|
||||||
|
|
||||||
export default function ImageToImagePanel() {
|
export default function ImageToImagePanel() {
|
||||||
const showAdvancedOptions = useAppSelector(
|
const showAdvancedOptions = useAppSelector(
|
||||||
@ -45,14 +46,14 @@ export default function ImageToImagePanel() {
|
|||||||
options: <UpscaleOptions />,
|
options: <UpscaleOptions />,
|
||||||
},
|
},
|
||||||
other: {
|
other: {
|
||||||
header: <OutputHeader /> ,
|
header: <OutputHeader />,
|
||||||
feature: Feature.OTHER,
|
feature: Feature.OTHER,
|
||||||
options: <OutputOptions />,
|
options: <OutputOptions />,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="image-to-image-panel">
|
<InvokeOptionsPanel>
|
||||||
<PromptInput />
|
<PromptInput />
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
@ -65,6 +66,6 @@ export default function ImageToImagePanel() {
|
|||||||
{showAdvancedOptions ? (
|
{showAdvancedOptions ? (
|
||||||
<OptionsAccordion accordionInfo={imageToImageAccordions} />
|
<OptionsAccordion accordionInfo={imageToImageAccordions} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</InvokeOptionsPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@
|
|||||||
|
|
||||||
// Overrides
|
// Overrides
|
||||||
.inpainting-workarea-overrides {
|
.inpainting-workarea-overrides {
|
||||||
.image-gallery-area {
|
.image-gallery-wrapper {
|
||||||
.chakra-popover__popper {
|
.chakra-popover__popper {
|
||||||
inset: 0 auto auto -75px !important;
|
inset: 0 auto auto -75px !important;
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import MainOptions from '../../options/MainOptions/MainOptions';
|
|||||||
import OptionsAccordion from '../../options/OptionsAccordion';
|
import OptionsAccordion from '../../options/OptionsAccordion';
|
||||||
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
||||||
import PromptInput from '../../options/PromptInput/PromptInput';
|
import PromptInput from '../../options/PromptInput/PromptInput';
|
||||||
|
import InvokeOptionsPanel from '../InvokeOptionsPanel';
|
||||||
|
|
||||||
export default function InpaintingPanel() {
|
export default function InpaintingPanel() {
|
||||||
const showAdvancedOptions = useAppSelector(
|
const showAdvancedOptions = useAppSelector(
|
||||||
@ -45,7 +46,7 @@ export default function InpaintingPanel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="image-to-image-panel">
|
<InvokeOptionsPanel>
|
||||||
<PromptInput />
|
<PromptInput />
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
@ -58,6 +59,6 @@ export default function InpaintingPanel() {
|
|||||||
{showAdvancedOptions ? (
|
{showAdvancedOptions ? (
|
||||||
<OptionsAccordion accordionInfo={imageToImageAccordions} />
|
<OptionsAccordion accordionInfo={imageToImageAccordions} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</InvokeOptionsPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
51
frontend/src/features/tabs/InvokeOptionsPanel.scss
Normal file
51
frontend/src/features/tabs/InvokeOptionsPanel.scss
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
@use '../../styles/Mixins/' as *;
|
||||||
|
|
||||||
|
.options-panel-wrapper-enter {
|
||||||
|
transform: translateX(-150%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-panel-wrapper-enter-active {
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: all 120ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-panel-wrapper-exit {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-panel-wrapper-exit-active {
|
||||||
|
transform: translateX(-150%);
|
||||||
|
transition: all 120ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-panel-wrapper {
|
||||||
|
z-index: 20;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
grid-auto-rows: max-content;
|
||||||
|
height: $app-content-height;
|
||||||
|
width: $options-bar-max-width;
|
||||||
|
max-width: $options-bar-max-width;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.options-panel {
|
||||||
|
display: grid;
|
||||||
|
row-gap: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
@include HideScrollbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-pinned='false'] {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
box-shadow: 0 0 1rem var(--text-color-a3);
|
||||||
|
width: calc($options-bar-max-width + 2rem);
|
||||||
|
max-width: calc($options-bar-max-width + 2rem);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.options-panel {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
137
frontend/src/features/tabs/InvokeOptionsPanel.tsx
Normal file
137
frontend/src/features/tabs/InvokeOptionsPanel.tsx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { MouseEvent, ReactNode, useEffect, useRef } from 'react';
|
||||||
|
import { BsPinAngleFill } from 'react-icons/bs';
|
||||||
|
import { CSSTransition } from 'react-transition-group';
|
||||||
|
import { RootState, useAppDispatch, useAppSelector } from '../../app/store';
|
||||||
|
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||||
|
import {
|
||||||
|
OptionsState,
|
||||||
|
setOptionsPanelScrollPosition,
|
||||||
|
setShouldHoldOptionsPanelOpen,
|
||||||
|
setShouldPinOptionsPanel,
|
||||||
|
setShouldShowOptionsPanel,
|
||||||
|
} from '../options/optionsSlice';
|
||||||
|
import { setNeedsCache } from './Inpainting/inpaintingSlice';
|
||||||
|
|
||||||
|
type Props = { children: ReactNode };
|
||||||
|
|
||||||
|
const optionsPanelSelector = createSelector(
|
||||||
|
(state: RootState) => state.options,
|
||||||
|
(options: OptionsState) => {
|
||||||
|
const {
|
||||||
|
shouldShowOptionsPanel,
|
||||||
|
shouldHoldOptionsPanelOpen,
|
||||||
|
shouldPinOptionsPanel,
|
||||||
|
optionsPanelScrollPosition,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return {
|
||||||
|
shouldShowOptionsPanel,
|
||||||
|
shouldHoldOptionsPanelOpen,
|
||||||
|
shouldPinOptionsPanel,
|
||||||
|
optionsPanelScrollPosition,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const InvokeOptionsPanel = (props: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const {
|
||||||
|
shouldShowOptionsPanel,
|
||||||
|
shouldHoldOptionsPanelOpen,
|
||||||
|
shouldPinOptionsPanel,
|
||||||
|
optionsPanelScrollPosition,
|
||||||
|
} = useAppSelector(optionsPanelSelector);
|
||||||
|
|
||||||
|
const optionsPanelRef = useRef<HTMLDivElement>(null);
|
||||||
|
const optionsPanelContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const timeoutIdRef = useRef<number | null>(null);
|
||||||
|
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
const handleCloseOptionsPanel = () => {
|
||||||
|
dispatch(
|
||||||
|
setOptionsPanelScrollPosition(
|
||||||
|
optionsPanelContainerRef.current
|
||||||
|
? optionsPanelContainerRef.current.scrollTop
|
||||||
|
: 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
dispatch(setShouldShowOptionsPanel(false));
|
||||||
|
dispatch(setShouldHoldOptionsPanelOpen(false));
|
||||||
|
shouldPinOptionsPanel && dispatch(setNeedsCache(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCloseOptionsPanelTimer = () => {
|
||||||
|
timeoutIdRef.current = window.setTimeout(
|
||||||
|
() => handleCloseOptionsPanel(),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelCloseOptionsPanelTimer = () => {
|
||||||
|
timeoutIdRef.current && window.clearTimeout(timeoutIdRef.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickPinOptionsPanel = () => {
|
||||||
|
dispatch(setShouldPinOptionsPanel(!shouldPinOptionsPanel));
|
||||||
|
dispatch(setNeedsCache(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
// // set gallery scroll position
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (!optionsPanelContainerRef.current) return;
|
||||||
|
// optionsPanelContainerRef.current.scrollTop = optionsPanelScrollPosition;
|
||||||
|
// }, [optionsPanelScrollPosition, shouldShowOptionsPanel]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CSSTransition
|
||||||
|
nodeRef={optionsPanelRef}
|
||||||
|
in={
|
||||||
|
shouldShowOptionsPanel ||
|
||||||
|
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
|
||||||
|
}
|
||||||
|
unmountOnExit
|
||||||
|
timeout={200}
|
||||||
|
classNames="options-panel-wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="options-panel-wrapper"
|
||||||
|
data-pinned={shouldPinOptionsPanel}
|
||||||
|
tabIndex={1}
|
||||||
|
ref={optionsPanelRef}
|
||||||
|
onMouseEnter={
|
||||||
|
!shouldPinOptionsPanel ? cancelCloseOptionsPanelTimer : undefined
|
||||||
|
}
|
||||||
|
onMouseOver={
|
||||||
|
!shouldPinOptionsPanel ? cancelCloseOptionsPanelTimer : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="options-panel"
|
||||||
|
ref={optionsPanelContainerRef}
|
||||||
|
onMouseLeave={(e: MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (e.target !== optionsPanelContainerRef.current) {
|
||||||
|
cancelCloseOptionsPanelTimer();
|
||||||
|
} else {
|
||||||
|
!shouldPinOptionsPanel && setCloseOptionsPanelTimer();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IAIIconButton
|
||||||
|
size={'sm'}
|
||||||
|
aria-label={'Pin Options Panel'}
|
||||||
|
tooltip={'Pin Options Panel (Shift+P)'}
|
||||||
|
onClick={handleClickPinOptionsPanel}
|
||||||
|
icon={<BsPinAngleFill />}
|
||||||
|
data-selected={shouldPinOptionsPanel}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CSSTransition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InvokeOptionsPanel;
|
@ -4,13 +4,15 @@
|
|||||||
display: grid !important;
|
display: grid !important;
|
||||||
grid-template-columns: min-content auto;
|
grid-template-columns: min-content auto;
|
||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
|
// height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-tabs-list {
|
.app-tabs-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
row-gap: 0.3rem;
|
row-gap: 0.3rem;
|
||||||
grid-auto-rows: max-content;
|
grid-auto-rows: min-content;
|
||||||
color: var(--tab-list-text-inactive);
|
color: var(--tab-list-text-inactive);
|
||||||
|
z-index: 15;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
@ -39,5 +41,6 @@
|
|||||||
.app-tabs-panels {
|
.app-tabs-panels {
|
||||||
.app-tabs-panel {
|
.app-tabs-panel {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,6 @@
|
|||||||
column-gap: 0.5rem;
|
column-gap: 0.5rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.workarea-options-panel {
|
|
||||||
width: $options-bar-max-width;
|
|
||||||
max-width: $options-bar-max-width;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workarea-split-view {
|
.workarea-split-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { RootState, useAppSelector } from '../../app/store';
|
|
||||||
import ImageGallery from '../gallery/ImageGallery';
|
import ImageGallery from '../gallery/ImageGallery';
|
||||||
import ShowHideGalleryButton from '../gallery/ShowHideGalleryButton';
|
|
||||||
|
|
||||||
type InvokeWorkareaProps = {
|
type InvokeWorkareaProps = {
|
||||||
optionsPanel: ReactNode;
|
optionsPanel: ReactNode;
|
||||||
@ -12,9 +10,6 @@ type InvokeWorkareaProps = {
|
|||||||
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
||||||
const { optionsPanel, children, styleClass } = props;
|
const { optionsPanel, children, styleClass } = props;
|
||||||
|
|
||||||
const { shouldShowGallery, shouldHoldGalleryOpen, shouldPinGallery } =
|
|
||||||
useAppSelector((state: RootState) => state.gallery);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
@ -22,13 +17,10 @@ const InvokeWorkarea = (props: InvokeWorkareaProps) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="workarea-main">
|
<div className="workarea-main">
|
||||||
<div className="workarea-options-panel">{optionsPanel}</div>
|
{optionsPanel}
|
||||||
{children}
|
{children}
|
||||||
<ImageGallery />
|
<ImageGallery />
|
||||||
</div>
|
</div>
|
||||||
{!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && (
|
|
||||||
<ShowHideGalleryButton />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
26
frontend/src/features/tabs/ShowHideGalleryButton.tsx
Normal file
26
frontend/src/features/tabs/ShowHideGalleryButton.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { MdPhotoLibrary } from 'react-icons/md';
|
||||||
|
import { useAppDispatch } from '../../app/store';
|
||||||
|
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||||
|
import { setShouldShowGallery } from '../gallery/gallerySlice';
|
||||||
|
|
||||||
|
const ShowHideGalleryButton = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleShowGallery = () => {
|
||||||
|
dispatch(setShouldShowGallery(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Show Gallery (G)"
|
||||||
|
tooltipPlacement="top"
|
||||||
|
aria-label="Show Gallery"
|
||||||
|
styleClass="floating-show-hide-button right"
|
||||||
|
onMouseOver={handleShowGallery}
|
||||||
|
>
|
||||||
|
<MdPhotoLibrary />
|
||||||
|
</IAIIconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowHideGalleryButton;
|
26
frontend/src/features/tabs/ShowHideOptionsPanelButton.tsx
Normal file
26
frontend/src/features/tabs/ShowHideOptionsPanelButton.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { IoMdOptions } from 'react-icons/io';
|
||||||
|
import { useAppDispatch } from '../../app/store';
|
||||||
|
import IAIIconButton from '../../common/components/IAIIconButton';
|
||||||
|
import { setShouldShowOptionsPanel } from '../options/optionsSlice';
|
||||||
|
|
||||||
|
const ShowHideOptionsPanelButton = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleShowOptionsPanel = () => {
|
||||||
|
dispatch(setShouldShowOptionsPanel(true));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip="Show Options Panel (G)"
|
||||||
|
tooltipPlacement="top"
|
||||||
|
aria-label="Show Options Panel"
|
||||||
|
styleClass="floating-show-hide-button left"
|
||||||
|
onMouseOver={handleShowOptionsPanel}
|
||||||
|
>
|
||||||
|
<IoMdOptions />
|
||||||
|
</IAIIconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShowHideOptionsPanelButton;
|
@ -4,12 +4,3 @@
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-to-image-panel {
|
|
||||||
display: grid;
|
|
||||||
row-gap: 1rem;
|
|
||||||
grid-auto-rows: max-content;
|
|
||||||
height: $app-content-height;
|
|
||||||
overflow-y: scroll;
|
|
||||||
@include HideScrollbar;
|
|
||||||
}
|
|
||||||
|
@ -15,6 +15,7 @@ import MainOptions from '../../options/MainOptions/MainOptions';
|
|||||||
import OptionsAccordion from '../../options/OptionsAccordion';
|
import OptionsAccordion from '../../options/OptionsAccordion';
|
||||||
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
import ProcessButtons from '../../options/ProcessButtons/ProcessButtons';
|
||||||
import PromptInput from '../../options/PromptInput/PromptInput';
|
import PromptInput from '../../options/PromptInput/PromptInput';
|
||||||
|
import InvokeOptionsPanel from '../InvokeOptionsPanel';
|
||||||
|
|
||||||
export default function TextToImagePanel() {
|
export default function TextToImagePanel() {
|
||||||
const showAdvancedOptions = useAppSelector(
|
const showAdvancedOptions = useAppSelector(
|
||||||
@ -50,7 +51,7 @@ export default function TextToImagePanel() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-to-image-panel">
|
<InvokeOptionsPanel>
|
||||||
<PromptInput />
|
<PromptInput />
|
||||||
<ProcessButtons />
|
<ProcessButtons />
|
||||||
<MainOptions />
|
<MainOptions />
|
||||||
@ -58,6 +59,6 @@ export default function TextToImagePanel() {
|
|||||||
{showAdvancedOptions ? (
|
{showAdvancedOptions ? (
|
||||||
<OptionsAccordion accordionInfo={textToImageAccordions} />
|
<OptionsAccordion accordionInfo={textToImageAccordions} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</InvokeOptionsPanel>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
--destructive-color: rgb(185, 55, 55);
|
--destructive-color: rgb(185, 55, 55);
|
||||||
--destructive-color-hover: rgb(255, 75, 75);
|
--destructive-color-hover: rgb(255, 75, 75);
|
||||||
|
|
||||||
|
--text-color-a3: rgba(255, 255, 255, 0.3);
|
||||||
|
|
||||||
// Error status colors
|
// Error status colors
|
||||||
--border-color-invalid: rgb(255, 80, 50);
|
--border-color-invalid: rgb(255, 80, 50);
|
||||||
--box-shadow-color-invalid: rgb(210, 30, 10);
|
--box-shadow-color-invalid: rgb(210, 30, 10);
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
--destructive-color: rgb(237, 51, 51);
|
--destructive-color: rgb(237, 51, 51);
|
||||||
--destructive-color-hover: rgb(255, 55, 55);
|
--destructive-color-hover: rgb(255, 55, 55);
|
||||||
|
|
||||||
|
--text-color-a3: rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
// Error status colors
|
// Error status colors
|
||||||
--border-color-invalid: rgb(255, 80, 50);
|
--border-color-invalid: rgb(255, 80, 50);
|
||||||
--box-shadow-color-invalid: none;
|
--box-shadow-color-invalid: none;
|
||||||
|
@ -31,15 +31,16 @@
|
|||||||
@use '../features/gallery/ImageGallery.scss';
|
@use '../features/gallery/ImageGallery.scss';
|
||||||
@use '../features/gallery/HoverableImage.scss';
|
@use '../features/gallery/HoverableImage.scss';
|
||||||
@use '../features/gallery/InvokePopover.scss';
|
@use '../features/gallery/InvokePopover.scss';
|
||||||
@use '../features/gallery/ShowHideGalleryButton.scss';
|
|
||||||
@use '../features/gallery/ImageMetaDataViewer/ImageMetadataViewer.scss';
|
@use '../features/gallery/ImageMetaDataViewer/ImageMetadataViewer.scss';
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
@use '../features/tabs/InvokeTabs.scss';
|
@use '../features/tabs/InvokeTabs.scss';
|
||||||
@use '../features/tabs/InvokeWorkarea.scss';
|
@use '../features/tabs/InvokeWorkarea.scss';
|
||||||
|
@use '../features/tabs/InvokeOptionsPanel.scss';
|
||||||
@use '../features/tabs/TextToImage/TextToImage.scss';
|
@use '../features/tabs/TextToImage/TextToImage.scss';
|
||||||
@use '../features/tabs/ImageToImage/ImageToImage.scss';
|
@use '../features/tabs/ImageToImage/ImageToImage.scss';
|
||||||
@use '../features/tabs/Inpainting/Inpainting.scss';
|
@use '../features/tabs/Inpainting/Inpainting.scss';
|
||||||
|
@use '../features/tabs/FloatingShowHideButton.scss';
|
||||||
|
|
||||||
// Component Shared
|
// Component Shared
|
||||||
@use '../common/components/IAINumberInput.scss';
|
@use '../common/components/IAINumberInput.scss';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user