mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into bbox-ar
This commit is contained in:
199
invokeai/frontend/web/dist/assets/App-9a48e001.js
vendored
199
invokeai/frontend/web/dist/assets/App-9a48e001.js
vendored
File diff suppressed because one or more lines are too long
199
invokeai/frontend/web/dist/assets/App-a44d46fe.js
vendored
Normal file
199
invokeai/frontend/web/dist/assets/App-a44d46fe.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
invokeai/frontend/web/dist/assets/MantineProvider-8988d217.js
vendored
Normal file
1
invokeai/frontend/web/dist/assets/MantineProvider-8988d217.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
125
invokeai/frontend/web/dist/assets/index-078526aa.js
vendored
Normal file
125
invokeai/frontend/web/dist/assets/index-078526aa.js
vendored
Normal file
File diff suppressed because one or more lines are too long
125
invokeai/frontend/web/dist/assets/index-581af3d4.js
vendored
125
invokeai/frontend/web/dist/assets/index-581af3d4.js
vendored
File diff suppressed because one or more lines are too long
2
invokeai/frontend/web/dist/index.html
vendored
2
invokeai/frontend/web/dist/index.html
vendored
@ -12,7 +12,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="./assets/index-581af3d4.js"></script>
|
||||
<script type="module" crossorigin src="./assets/index-078526aa.js"></script>
|
||||
</head>
|
||||
|
||||
<body dir="ltr">
|
||||
|
12
invokeai/frontend/web/dist/locales/en.json
vendored
12
invokeai/frontend/web/dist/locales/en.json
vendored
@ -53,7 +53,7 @@
|
||||
"linear": "Linear",
|
||||
"nodes": "Node Editor",
|
||||
"batch": "Batch Manager",
|
||||
"modelmanager": "Model Manager",
|
||||
"modelManager": "Model Manager",
|
||||
"postprocessing": "Post Processing",
|
||||
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
|
||||
"postProcessing": "Post Processing",
|
||||
@ -527,7 +527,9 @@
|
||||
"showOptionsPanel": "Show Options Panel",
|
||||
"hidePreview": "Hide Preview",
|
||||
"showPreview": "Show Preview",
|
||||
"controlNetControlMode": "Control Mode"
|
||||
"controlNetControlMode": "Control Mode",
|
||||
"clipSkip": "Clip Skip",
|
||||
"aspectRatio": "Ratio"
|
||||
},
|
||||
"settings": {
|
||||
"models": "Models",
|
||||
@ -551,7 +553,8 @@
|
||||
"generation": "Generation",
|
||||
"ui": "User Interface",
|
||||
"favoriteSchedulers": "Favorite Schedulers",
|
||||
"favoriteSchedulersPlaceholder": "No schedulers favorited"
|
||||
"favoriteSchedulersPlaceholder": "No schedulers favorited",
|
||||
"showAdvancedOptions": "Show Advanced Options"
|
||||
},
|
||||
"toast": {
|
||||
"serverError": "Server Error",
|
||||
@ -669,6 +672,7 @@
|
||||
},
|
||||
"ui": {
|
||||
"showProgressImages": "Show Progress Images",
|
||||
"hideProgressImages": "Hide Progress Images"
|
||||
"hideProgressImages": "Hide Progress Images",
|
||||
"swapSizes": "Swap Sizes"
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@
|
||||
"linear": "Linear",
|
||||
"nodes": "Node Editor",
|
||||
"batch": "Batch Manager",
|
||||
"modelmanager": "Model Manager",
|
||||
"modelManager": "Model Manager",
|
||||
"postprocessing": "Post Processing",
|
||||
"nodesDesc": "A node based system for the generation of images is under development currently. Stay tuned for updates about this amazing feature.",
|
||||
"postProcessing": "Post Processing",
|
||||
@ -593,7 +593,10 @@
|
||||
"metadataLoadFailed": "Failed to load metadata",
|
||||
"initialImageSet": "Initial Image Set",
|
||||
"initialImageNotSet": "Initial Image Not Set",
|
||||
"initialImageNotSetDesc": "Could not load initial image"
|
||||
"initialImageNotSetDesc": "Could not load initial image",
|
||||
"nodesSaved": "Nodes Saved",
|
||||
"nodesLoaded": "Nodes Loaded",
|
||||
"nodesLoadedFailed": "Failed To Load Nodes"
|
||||
},
|
||||
"tooltip": {
|
||||
"feature": {
|
||||
@ -674,5 +677,10 @@
|
||||
"showProgressImages": "Show Progress Images",
|
||||
"hideProgressImages": "Hide Progress Images",
|
||||
"swapSizes": "Swap Sizes"
|
||||
},
|
||||
"nodes": {
|
||||
"reloadSchema": "Reload Schema",
|
||||
"saveNodes": "Save Nodes",
|
||||
"loadNodes": "Load Nodes"
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,8 @@ export type AppFeature =
|
||||
export type SDFeature =
|
||||
| 'controlNet'
|
||||
| 'noise'
|
||||
| 'perlinNoise'
|
||||
| 'noiseThreshold'
|
||||
| 'variation'
|
||||
| 'symmetry'
|
||||
| 'seamless'
|
||||
|
@ -53,13 +53,15 @@ const GalleryImage = (props: HoverableImageProps) => {
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: MouseEvent<HTMLDivElement>) => {
|
||||
if (e.shiftKey) {
|
||||
dispatch(imageRangeEndSelected(props.imageDTO.image_name));
|
||||
} else if (e.ctrlKey || e.metaKey) {
|
||||
dispatch(imageSelectionToggled(props.imageDTO.image_name));
|
||||
} else {
|
||||
dispatch(imageSelected(props.imageDTO.image_name));
|
||||
}
|
||||
// multiselect disabled for now
|
||||
// if (e.shiftKey) {
|
||||
// dispatch(imageRangeEndSelected(props.imageDTO.image_name));
|
||||
// } else if (e.ctrlKey || e.metaKey) {
|
||||
// dispatch(imageSelectionToggled(props.imageDTO.image_name));
|
||||
// } else {
|
||||
// dispatch(imageSelected(props.imageDTO.image_name));
|
||||
// }
|
||||
dispatch(imageSelected(props.imageDTO.image_name));
|
||||
},
|
||||
[dispatch, props.imageDTO.image_name]
|
||||
);
|
||||
@ -121,6 +123,7 @@ const GalleryImage = (props: HoverableImageProps) => {
|
||||
// withResetIcon // removed bc it's too easy to accidentally delete images
|
||||
isDropDisabled={true}
|
||||
isUploadDisabled={true}
|
||||
thumbnail={true}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
OnConnectEnd,
|
||||
OnConnectStart,
|
||||
OnEdgesChange,
|
||||
OnInit,
|
||||
OnNodesChange,
|
||||
ReactFlow,
|
||||
} from 'reactflow';
|
||||
@ -16,6 +17,7 @@ import {
|
||||
connectionStarted,
|
||||
edgesChanged,
|
||||
nodesChanged,
|
||||
setEditorInstance,
|
||||
} from '../store/nodesSlice';
|
||||
import { InvocationComponent } from './InvocationComponent';
|
||||
import ProgressImageNode from './ProgressImageNode';
|
||||
@ -67,6 +69,14 @@ export const Flow = () => {
|
||||
dispatch(connectionEnded());
|
||||
}, [dispatch]);
|
||||
|
||||
const onInit: OnInit = useCallback(
|
||||
(v) => {
|
||||
dispatch(setEditorInstance(v));
|
||||
if (v) v.fitView();
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodeTypes={nodeTypes}
|
||||
@ -77,6 +87,7 @@ export const Flow = () => {
|
||||
onConnectStart={onConnectStart}
|
||||
onConnect={onConnect}
|
||||
onConnectEnd={onConnectEnd}
|
||||
onInit={onInit}
|
||||
defaultEdgeOptions={{
|
||||
style: { strokeWidth: 2 },
|
||||
}}
|
||||
|
@ -1,25 +1,21 @@
|
||||
import { HStack } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { Panel } from 'reactflow';
|
||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||
import NodeInvokeButton from '../ui/NodeInvokeButton';
|
||||
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
|
||||
import { memo } from 'react';
|
||||
import { Panel } from 'reactflow';
|
||||
import LoadNodesButton from '../ui/LoadNodesButton';
|
||||
import NodeInvokeButton from '../ui/NodeInvokeButton';
|
||||
import ReloadSchemaButton from '../ui/ReloadSchemaButton';
|
||||
import SaveNodesButton from '../ui/SaveNodesButton';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleReloadSchema = useCallback(() => {
|
||||
dispatch(receivedOpenAPISchema());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Panel position="top-center">
|
||||
<HStack>
|
||||
<NodeInvokeButton />
|
||||
<CancelButton />
|
||||
<IAIButton onClick={handleReloadSchema}>Reload Schema</IAIButton>
|
||||
<ReloadSchemaButton />
|
||||
<SaveNodesButton />
|
||||
<LoadNodesButton />
|
||||
</HStack>
|
||||
</Panel>
|
||||
);
|
||||
|
@ -0,0 +1,79 @@
|
||||
import { FileButton } from '@mantine/core';
|
||||
import { makeToast } from 'app/components/Toaster';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { loadFileEdges, loadFileNodes } from 'features/nodes/store/nodesSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaUpload } from 'react-icons/fa';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
|
||||
const LoadNodesButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const { fitView } = useReactFlow();
|
||||
|
||||
const uploadedFileRef = useRef<() => void>(null);
|
||||
|
||||
const restoreJSONToEditor = useCallback(
|
||||
(v: File | null) => {
|
||||
if (!v) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = async () => {
|
||||
const json = reader.result;
|
||||
const retrievedNodeTree = await JSON.parse(String(json));
|
||||
|
||||
if (!retrievedNodeTree) {
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({
|
||||
title: t('toast.nodesLoadedFailed'),
|
||||
status: 'error',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (retrievedNodeTree) {
|
||||
dispatch(loadFileNodes(retrievedNodeTree.nodes));
|
||||
dispatch(loadFileEdges(retrievedNodeTree.edges));
|
||||
fitView();
|
||||
|
||||
dispatch(
|
||||
addToast(
|
||||
makeToast({ title: t('toast.nodesLoaded'), status: 'success' })
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
reader.abort();
|
||||
};
|
||||
|
||||
reader.readAsText(v);
|
||||
|
||||
// Cleanup
|
||||
uploadedFileRef.current?.();
|
||||
},
|
||||
[fitView, dispatch, t]
|
||||
);
|
||||
return (
|
||||
<FileButton
|
||||
resetRef={uploadedFileRef}
|
||||
accept="application/json"
|
||||
onChange={restoreJSONToEditor}
|
||||
>
|
||||
{(props) => (
|
||||
<IAIIconButton
|
||||
icon={<FaUpload />}
|
||||
tooltip={t('nodes.loadNodes')}
|
||||
aria-label={t('nodes.loadNodes')}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</FileButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LoadNodesButton);
|
@ -0,0 +1,24 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSyncAlt } from 'react-icons/fa';
|
||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||
|
||||
export default function ReloadSchemaButton() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleReloadSchema = useCallback(() => {
|
||||
dispatch(receivedOpenAPISchema());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
icon={<FaSyncAlt />}
|
||||
tooltip={t('nodes.reloadSchema')}
|
||||
aria-label={t('nodes.reloadSchema')}
|
||||
onClick={handleReloadSchema}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { map, omit } from 'lodash-es';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSave } from 'react-icons/fa';
|
||||
|
||||
const SaveNodesButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const editorInstance = useAppSelector(
|
||||
(state: RootState) => state.nodes.editorInstance
|
||||
);
|
||||
|
||||
const saveEditorToJSON = useCallback(() => {
|
||||
if (editorInstance) {
|
||||
const editorState = editorInstance.toObject();
|
||||
|
||||
editorState.edges = map(editorState.edges, (edge) => {
|
||||
return omit(edge, ['style']);
|
||||
});
|
||||
|
||||
const nodeSetupJSON = new Blob([JSON.stringify(editorState)]);
|
||||
const nodeDownloadElement = document.createElement('a');
|
||||
nodeDownloadElement.href = URL.createObjectURL(nodeSetupJSON);
|
||||
nodeDownloadElement.download = 'MyNodes.json';
|
||||
document.body.appendChild(nodeDownloadElement);
|
||||
nodeDownloadElement.click();
|
||||
// Cleanup
|
||||
nodeDownloadElement.remove();
|
||||
}
|
||||
}, [editorInstance]);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
icon={<FaSave />}
|
||||
fontSize={18}
|
||||
tooltip={t('nodes.saveNodes')}
|
||||
aria-label={t('nodes.saveNodes')}
|
||||
onClick={saveEditorToJSON}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SaveNodesButton);
|
@ -13,6 +13,7 @@ import {
|
||||
Node,
|
||||
NodeChange,
|
||||
OnConnectStartParams,
|
||||
ReactFlowInstance,
|
||||
} from 'reactflow';
|
||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||
import { ImageField } from 'services/api/types';
|
||||
@ -25,6 +26,7 @@ export type NodesState = {
|
||||
invocationTemplates: Record<string, InvocationTemplate>;
|
||||
connectionStartParams: OnConnectStartParams | null;
|
||||
shouldShowGraphOverlay: boolean;
|
||||
editorInstance: ReactFlowInstance | undefined;
|
||||
};
|
||||
|
||||
export const initialNodesState: NodesState = {
|
||||
@ -34,6 +36,7 @@ export const initialNodesState: NodesState = {
|
||||
invocationTemplates: {},
|
||||
connectionStartParams: null,
|
||||
shouldShowGraphOverlay: false,
|
||||
editorInstance: undefined,
|
||||
};
|
||||
|
||||
const nodesSlice = createSlice({
|
||||
@ -121,6 +124,15 @@ const nodesSlice = createSlice({
|
||||
nodeEditorReset: () => {
|
||||
return { ...initialNodesState };
|
||||
},
|
||||
setEditorInstance: (state, action) => {
|
||||
state.editorInstance = action.payload;
|
||||
},
|
||||
loadFileNodes: (state, action: PayloadAction<Node<InvocationValue>[]>) => {
|
||||
state.nodes = action.payload;
|
||||
},
|
||||
loadFileEdges: (state, action: PayloadAction<Edge[]>) => {
|
||||
state.edges = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
||||
@ -141,6 +153,9 @@ export const {
|
||||
nodeTemplatesBuilt,
|
||||
nodeEditorReset,
|
||||
imageCollectionFieldValueChanged,
|
||||
setEditorInstance,
|
||||
loadFileNodes,
|
||||
loadFileEdges,
|
||||
} = nodesSlice.actions;
|
||||
|
||||
export default nodesSlice.reducer;
|
||||
|
@ -3,6 +3,7 @@ import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { setAspectRatio } from 'features/ui/store/uiSlice';
|
||||
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
||||
|
||||
const aspectRatios = [
|
||||
{ name: 'Free', value: null },
|
||||
@ -17,6 +18,10 @@ export default function ParamAspectRatio() {
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const shouldFitToWidthHeight = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldFitToWidthHeight
|
||||
);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
return (
|
||||
<Flex gap={2} flexGrow={1}>
|
||||
@ -26,6 +31,9 @@ export default function ParamAspectRatio() {
|
||||
key={ratio.name}
|
||||
size="sm"
|
||||
isChecked={aspectRatio === ratio.value}
|
||||
isDisabled={
|
||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||
}
|
||||
onClick={() => dispatch(setAspectRatio(ratio.value))}
|
||||
>
|
||||
{ratio.name}
|
||||
|
@ -8,6 +8,7 @@ import { MdOutlineSwapVert } from 'react-icons/md';
|
||||
import ParamAspectRatio from './ParamAspectRatio';
|
||||
import ParamHeight from './ParamHeight';
|
||||
import ParamWidth from './ParamWidth';
|
||||
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
||||
|
||||
export default function ParamSize() {
|
||||
const { t } = useTranslation();
|
||||
@ -15,6 +16,7 @@ export default function ParamSize() {
|
||||
const shouldFitToWidthHeight = useAppSelector(
|
||||
(state: RootState) => state.generation.shouldFitToWidthHeight
|
||||
);
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
@ -50,13 +52,24 @@ export default function ParamSize() {
|
||||
size="sm"
|
||||
icon={<MdOutlineSwapVert />}
|
||||
fontSize={20}
|
||||
isDisabled={
|
||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||
}
|
||||
onClick={() => dispatch(toggleSize())}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex gap={2} alignItems="center">
|
||||
<Flex gap={2} flexDirection="column" width="full">
|
||||
<ParamWidth isDisabled={!shouldFitToWidthHeight} />
|
||||
<ParamHeight isDisabled={!shouldFitToWidthHeight} />
|
||||
<ParamWidth
|
||||
isDisabled={
|
||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||
}
|
||||
/>
|
||||
<ParamHeight
|
||||
isDisabled={
|
||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -27,6 +27,9 @@ const ParamNoiseCollapse = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const isNoiseEnabled = useFeatureStatus('noise').isFeatureEnabled;
|
||||
const isPerlinNoiseEnabled = useFeatureStatus('perlinNoise').isFeatureEnabled;
|
||||
const isNoiseThresholdEnabled =
|
||||
useFeatureStatus('noiseThreshold').isFeatureEnabled;
|
||||
|
||||
const { activeLabel } = useAppSelector(selector);
|
||||
|
||||
@ -42,8 +45,8 @@ const ParamNoiseCollapse = () => {
|
||||
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
|
||||
<ParamNoiseToggle />
|
||||
<ParamCpuNoiseToggle />
|
||||
<ParamPerlinNoise />
|
||||
<ParamNoiseThreshold />
|
||||
{isPerlinNoiseEnabled && <ParamPerlinNoise />}
|
||||
{isNoiseThresholdEnabled && <ParamNoiseThreshold />}
|
||||
</Flex>
|
||||
</IAICollapse>
|
||||
);
|
||||
|
@ -6,8 +6,15 @@ import { merge } from 'lodash-es';
|
||||
export const initialConfigState: AppConfig = {
|
||||
shouldUpdateImagesOnConnect: false,
|
||||
disabledTabs: [],
|
||||
disabledFeatures: [],
|
||||
disabledSDFeatures: [],
|
||||
disabledFeatures: ['lightbox', 'faceRestore'],
|
||||
disabledSDFeatures: [
|
||||
'variation',
|
||||
'seamless',
|
||||
'symmetry',
|
||||
'hires',
|
||||
'perlinNoise',
|
||||
'noiseThreshold',
|
||||
],
|
||||
canRestoreDeletedImagesFromBin: true,
|
||||
sd: {
|
||||
disabledControlNetModels: [],
|
||||
|
@ -38,6 +38,7 @@ import NodesTab from './tabs/Nodes/NodesTab';
|
||||
import ResizeHandle from './tabs/ResizeHandle';
|
||||
import TextToImageTab from './tabs/TextToImage/TextToImageTab';
|
||||
import UnifiedCanvasTab from './tabs/UnifiedCanvas/UnifiedCanvasTab';
|
||||
import { useFeatureStatus } from '../../system/hooks/useFeatureStatus';
|
||||
|
||||
export interface InvokeTabInfo {
|
||||
id: InvokeTabName;
|
||||
@ -107,6 +108,7 @@ const InvokeTabs = () => {
|
||||
const isLightBoxOpen = useAppSelector(
|
||||
(state: RootState) => state.lightbox.isLightboxOpen
|
||||
);
|
||||
const isLightboxEnabled = useFeatureStatus('lightbox').isFeatureEnabled;
|
||||
|
||||
const { shouldPinGallery, shouldPinParametersPanel, shouldShowGallery } =
|
||||
useAppSelector((state: RootState) => state.ui);
|
||||
@ -119,7 +121,9 @@ const InvokeTabs = () => {
|
||||
useHotkeys(
|
||||
'z',
|
||||
() => {
|
||||
dispatch(setIsLightboxOpen(!isLightBoxOpen));
|
||||
if (isLightboxEnabled) {
|
||||
dispatch(setIsLightboxOpen(!isLightBoxOpen));
|
||||
}
|
||||
},
|
||||
[isLightBoxOpen]
|
||||
);
|
||||
|
Reference in New Issue
Block a user