mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
chore(ui): format
Lots of changed bc the line length is now 120. May as well do it now.
This commit is contained in:
parent
b922ee566a
commit
189c430e46
@ -1,13 +1,7 @@
|
|||||||
{
|
{
|
||||||
"entry": ["src/main.tsx"],
|
"entry": ["src/main.tsx"],
|
||||||
"extensions": [".ts", ".tsx"],
|
"extensions": [".ts", ".tsx"],
|
||||||
"ignorePatterns": [
|
"ignorePatterns": ["**/node_modules/**", "dist/**", "public/**", "**/*.stories.tsx", "config/**"],
|
||||||
"**/node_modules/**",
|
|
||||||
"dist/**",
|
|
||||||
"public/**",
|
|
||||||
"**/*.stories.tsx",
|
|
||||||
"config/**"
|
|
||||||
],
|
|
||||||
"ignoreUnresolved": [],
|
"ignoreUnresolved": [],
|
||||||
"ignoreUnimported": ["src/i18.d.ts", "vite.config.ts", "src/vite-env.d.ts"],
|
"ignoreUnimported": ["src/i18.d.ts", "vite.config.ts", "src/vite-env.d.ts"],
|
||||||
"respectGitignore": true,
|
"respectGitignore": true,
|
||||||
|
@ -1463,9 +1463,7 @@
|
|||||||
},
|
},
|
||||||
"compositingCoherencePass": {
|
"compositingCoherencePass": {
|
||||||
"heading": "Coherence Pass",
|
"heading": "Coherence Pass",
|
||||||
"paragraphs": [
|
"paragraphs": ["A second round of denoising helps to composite the Inpainted/Outpainted image."]
|
||||||
"A second round of denoising helps to composite the Inpainted/Outpainted image."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"compositingCoherenceMode": {
|
"compositingCoherenceMode": {
|
||||||
"heading": "Mode",
|
"heading": "Mode",
|
||||||
@ -1473,10 +1471,7 @@
|
|||||||
},
|
},
|
||||||
"compositingCoherenceSteps": {
|
"compositingCoherenceSteps": {
|
||||||
"heading": "Steps",
|
"heading": "Steps",
|
||||||
"paragraphs": [
|
"paragraphs": ["Number of denoising steps used in the Coherence Pass.", "Same as the main Steps parameter."]
|
||||||
"Number of denoising steps used in the Coherence Pass.",
|
|
||||||
"Same as the main Steps parameter."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"compositingStrength": {
|
"compositingStrength": {
|
||||||
"heading": "Strength",
|
"heading": "Strength",
|
||||||
@ -1498,15 +1493,11 @@
|
|||||||
},
|
},
|
||||||
"controlNetControlMode": {
|
"controlNetControlMode": {
|
||||||
"heading": "Control Mode",
|
"heading": "Control Mode",
|
||||||
"paragraphs": [
|
"paragraphs": ["Lends more weight to either the prompt or ControlNet."]
|
||||||
"Lends more weight to either the prompt or ControlNet."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"controlNetResizeMode": {
|
"controlNetResizeMode": {
|
||||||
"heading": "Resize Mode",
|
"heading": "Resize Mode",
|
||||||
"paragraphs": [
|
"paragraphs": ["How the ControlNet image will be fit to the image output size."]
|
||||||
"How the ControlNet image will be fit to the image output size."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"controlNet": {
|
"controlNet": {
|
||||||
"heading": "ControlNet",
|
"heading": "ControlNet",
|
||||||
@ -1516,9 +1507,7 @@
|
|||||||
},
|
},
|
||||||
"controlNetWeight": {
|
"controlNetWeight": {
|
||||||
"heading": "Weight",
|
"heading": "Weight",
|
||||||
"paragraphs": [
|
"paragraphs": ["How strongly the ControlNet will impact the generated image."]
|
||||||
"How strongly the ControlNet will impact the generated image."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"dynamicPrompts": {
|
"dynamicPrompts": {
|
||||||
"heading": "Dynamic Prompts",
|
"heading": "Dynamic Prompts",
|
||||||
@ -1530,9 +1519,7 @@
|
|||||||
},
|
},
|
||||||
"dynamicPromptsMaxPrompts": {
|
"dynamicPromptsMaxPrompts": {
|
||||||
"heading": "Max Prompts",
|
"heading": "Max Prompts",
|
||||||
"paragraphs": [
|
"paragraphs": ["Limits the number of prompts that can be generated by Dynamic Prompts."]
|
||||||
"Limits the number of prompts that can be generated by Dynamic Prompts."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"dynamicPromptsSeedBehaviour": {
|
"dynamicPromptsSeedBehaviour": {
|
||||||
"heading": "Seed Behaviour",
|
"heading": "Seed Behaviour",
|
||||||
@ -1549,9 +1536,7 @@
|
|||||||
},
|
},
|
||||||
"lora": {
|
"lora": {
|
||||||
"heading": "LoRA Weight",
|
"heading": "LoRA Weight",
|
||||||
"paragraphs": [
|
"paragraphs": ["Higher LoRA weight will lead to larger impacts on the final image."]
|
||||||
"Higher LoRA weight will lead to larger impacts on the final image."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"noiseUseCPU": {
|
"noiseUseCPU": {
|
||||||
"heading": "Use CPU Noise",
|
"heading": "Use CPU Noise",
|
||||||
@ -1563,9 +1548,7 @@
|
|||||||
},
|
},
|
||||||
"paramCFGScale": {
|
"paramCFGScale": {
|
||||||
"heading": "CFG Scale",
|
"heading": "CFG Scale",
|
||||||
"paragraphs": [
|
"paragraphs": ["Controls how much your prompt influences the generation process."]
|
||||||
"Controls how much your prompt influences the generation process."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"paramCFGRescaleMultiplier": {
|
"paramCFGRescaleMultiplier": {
|
||||||
"heading": "CFG Rescale Multiplier",
|
"heading": "CFG Rescale Multiplier",
|
||||||
@ -1617,9 +1600,7 @@
|
|||||||
},
|
},
|
||||||
"paramVAE": {
|
"paramVAE": {
|
||||||
"heading": "VAE",
|
"heading": "VAE",
|
||||||
"paragraphs": [
|
"paragraphs": ["Model used for translating AI output into the final image."]
|
||||||
"Model used for translating AI output into the final image."
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"paramVAEPrecision": {
|
"paramVAEPrecision": {
|
||||||
"heading": "VAE Precision",
|
"heading": "VAE Precision",
|
||||||
|
@ -6,9 +6,7 @@ const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
|
|||||||
const OUTPUT_FILE = 'src/services/api/schema.ts';
|
const OUTPUT_FILE = 'src/services/api/schema.ts';
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
process.stdout.write(
|
process.stdout.write(`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...`);
|
||||||
`Generating types "${OPENAPI_URL}" --> "${OUTPUT_FILE}"...`
|
|
||||||
);
|
|
||||||
const types = await openapiTS(OPENAPI_URL, {
|
const types = await openapiTS(OPENAPI_URL, {
|
||||||
exportType: true,
|
exportType: true,
|
||||||
transform: (schemaObject) => {
|
transform: (schemaObject) => {
|
||||||
|
@ -45,8 +45,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
|||||||
useGlobalModifiersInit();
|
useGlobalModifiersInit();
|
||||||
useGlobalHotkeys();
|
useGlobalHotkeys();
|
||||||
|
|
||||||
const { dropzone, isHandlingUpload, setIsHandlingUpload } =
|
const { dropzone, isHandlingUpload, setIsHandlingUpload } = useFullscreenDropzone();
|
||||||
useFullscreenDropzone();
|
|
||||||
|
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
clearStorage();
|
clearStorage();
|
||||||
@ -70,10 +69,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary onReset={handleReset} FallbackComponent={AppErrorBoundaryFallback}>
|
||||||
onReset={handleReset}
|
|
||||||
FallbackComponent={AppErrorBoundaryFallback}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
id="invoke-app-wrapper"
|
id="invoke-app-wrapper"
|
||||||
w="100vw"
|
w="100vw"
|
||||||
@ -86,10 +82,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
|
|||||||
<InvokeTabs />
|
<InvokeTabs />
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{dropzone.isDragActive && isHandlingUpload && (
|
{dropzone.isDragActive && isHandlingUpload && (
|
||||||
<ImageUploadOverlay
|
<ImageUploadOverlay dropzone={dropzone} setIsHandlingUpload={setIsHandlingUpload} />
|
||||||
dropzone={dropzone}
|
|
||||||
setIsHandlingUpload={setIsHandlingUpload}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,19 +1,8 @@
|
|||||||
import {
|
import { Button, Flex, Heading, Link, Text, useToast } from '@invoke-ai/ui-library';
|
||||||
Button,
|
|
||||||
Flex,
|
|
||||||
Heading,
|
|
||||||
Link,
|
|
||||||
Text,
|
|
||||||
useToast,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import newGithubIssueUrl from 'new-github-issue-url';
|
import newGithubIssueUrl from 'new-github-issue-url';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { PiArrowCounterClockwiseBold, PiArrowSquareOutBold, PiCopyBold } from 'react-icons/pi';
|
||||||
PiArrowCounterClockwiseBold,
|
|
||||||
PiArrowSquareOutBold,
|
|
||||||
PiCopyBold,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
import { serializeError } from 'serialize-error';
|
import { serializeError } from 'serialize-error';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -44,22 +33,8 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
|||||||
[error.message, error.name]
|
[error.message, error.name]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex layerStyle="body" w="100vw" h="100vh" alignItems="center" justifyContent="center" p={4}>
|
||||||
layerStyle="body"
|
<Flex layerStyle="first" flexDir="column" borderRadius="base" justifyContent="center" gap={8} p={16}>
|
||||||
w="100vw"
|
|
||||||
h="100vh"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
p={4}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
layerStyle="first"
|
|
||||||
flexDir="column"
|
|
||||||
borderRadius="base"
|
|
||||||
justifyContent="center"
|
|
||||||
gap={8}
|
|
||||||
p={16}
|
|
||||||
>
|
|
||||||
<Heading>{t('common.somethingWentWrong')}</Heading>
|
<Heading>{t('common.somethingWentWrong')}</Heading>
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle="second"
|
layerStyle="second"
|
||||||
@ -75,19 +50,14 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
|
|||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={4}>
|
<Flex gap={4}>
|
||||||
<Button
|
<Button leftIcon={<PiArrowCounterClockwiseBold />} onClick={resetErrorBoundary}>
|
||||||
leftIcon={<PiArrowCounterClockwiseBold />}
|
|
||||||
onClick={resetErrorBoundary}
|
|
||||||
>
|
|
||||||
{t('accessibility.resetUI')}
|
{t('accessibility.resetUI')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button leftIcon={<PiCopyBold />} onClick={handleCopy}>
|
<Button leftIcon={<PiCopyBold />} onClick={handleCopy}>
|
||||||
{t('common.copyError')}
|
{t('common.copyError')}
|
||||||
</Button>
|
</Button>
|
||||||
<Link href={url} isExternal>
|
<Link href={url} isExternal>
|
||||||
<Button leftIcon={<PiArrowSquareOutBold />}>
|
<Button leftIcon={<PiArrowSquareOutBold />}>{t('accessibility.createIssue')}</Button>
|
||||||
{t('accessibility.createIssue')}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import '@fontsource-variable/inter';
|
import '@fontsource-variable/inter';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
|
||||||
import {
|
import { ChakraProvider, DarkMode, extendTheme, theme as _theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
|
||||||
ChakraProvider,
|
|
||||||
DarkMode,
|
|
||||||
extendTheme,
|
|
||||||
theme as _theme,
|
|
||||||
TOAST_OPTIONS,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { memo, useEffect, useMemo } from 'react';
|
import { memo, useEffect, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -36,10 +36,7 @@ const Toaster = () => {
|
|||||||
*/
|
*/
|
||||||
export const useAppToaster = () => {
|
export const useAppToaster = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const toaster = useCallback(
|
const toaster = useCallback((arg: MakeToastArg) => dispatch(addToast(makeToast(arg))), [dispatch]);
|
||||||
(arg: MakeToastArg) => dispatch(addToast(makeToast(arg))),
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return toaster;
|
return toaster;
|
||||||
};
|
};
|
||||||
|
@ -6,10 +6,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
|||||||
import type { MapStore } from 'nanostores';
|
import type { MapStore } from 'nanostores';
|
||||||
import { atom, map } from 'nanostores';
|
import { atom, map } from 'nanostores';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import type {
|
import type { ClientToServerEvents, ServerToClientEvents } from 'services/events/types';
|
||||||
ClientToServerEvents,
|
|
||||||
ServerToClientEvents,
|
|
||||||
} from 'services/events/types';
|
|
||||||
import { setEventListeners } from 'services/events/util/setEventListeners';
|
import { setEventListeners } from 'services/events/util/setEventListeners';
|
||||||
import type { ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
|
import type { ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
|
||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
@ -45,9 +42,7 @@ export const useSocketIO = () => {
|
|||||||
const socketOptions = useMemo(() => {
|
const socketOptions = useMemo(() => {
|
||||||
const options: Partial<ManagerOptions & SocketOptions> = {
|
const options: Partial<ManagerOptions & SocketOptions> = {
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
path: baseUrl
|
path: baseUrl ? '/ws/socket.io' : `${window.location.pathname}ws/socket.io`,
|
||||||
? '/ws/socket.io'
|
|
||||||
: `${window.location.pathname}ws/socket.io`,
|
|
||||||
autoConnect: false, // achtung! removing this breaks the dynamic middleware
|
autoConnect: false, // achtung! removing this breaks the dynamic middleware
|
||||||
forceNew: true,
|
forceNew: true,
|
||||||
};
|
};
|
||||||
@ -66,10 +61,7 @@ export const useSocketIO = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
|
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(socketUrl, socketOptions);
|
||||||
socketUrl,
|
|
||||||
socketOptions
|
|
||||||
);
|
|
||||||
setEventListeners({ dispatch, socket });
|
setEventListeners({ dispatch, socket });
|
||||||
socket.connect();
|
socket.connect();
|
||||||
|
|
||||||
|
@ -30,20 +30,11 @@ export type LoggerNamespace =
|
|||||||
| 'queue'
|
| 'queue'
|
||||||
| 'dnd';
|
| 'dnd';
|
||||||
|
|
||||||
export const logger = (namespace: LoggerNamespace) =>
|
export const logger = (namespace: LoggerNamespace) => $logger.get().child({ namespace });
|
||||||
$logger.get().child({ namespace });
|
|
||||||
|
|
||||||
export const zLogLevel = z.enum([
|
export const zLogLevel = z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
||||||
'trace',
|
|
||||||
'debug',
|
|
||||||
'info',
|
|
||||||
'warn',
|
|
||||||
'error',
|
|
||||||
'fatal',
|
|
||||||
]);
|
|
||||||
export type LogLevel = z.infer<typeof zLogLevel>;
|
export type LogLevel = z.infer<typeof zLogLevel>;
|
||||||
export const isLogLevel = (v: unknown): v is LogLevel =>
|
export const isLogLevel = (v: unknown): v is LogLevel => zLogLevel.safeParse(v).success;
|
||||||
zLogLevel.safeParse(v).success;
|
|
||||||
|
|
||||||
// Translate human-readable log levels to numbers, used for log filtering
|
// Translate human-readable log levels to numbers, used for log filtering
|
||||||
export const LOG_LEVEL_MAP: Record<LogLevel, number> = {
|
export const LOG_LEVEL_MAP: Record<LogLevel, number> = {
|
||||||
|
@ -17,10 +17,7 @@ export const useLogger = (namespace: LoggerNamespace) => {
|
|||||||
localStorage.setItem('ROARR_LOG', 'true');
|
localStorage.setItem('ROARR_LOG', 'true');
|
||||||
|
|
||||||
// Use a filter to show only logs of the given level
|
// Use a filter to show only logs of the given level
|
||||||
localStorage.setItem(
|
localStorage.setItem('ROARR_FILTER', `context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`);
|
||||||
'ROARR_FILTER',
|
|
||||||
`context.logLevel:>=${LOG_LEVEL_MAP[consoleLogLevel]}`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Disable console log output
|
// Disable console log output
|
||||||
localStorage.setItem('ROARR_LOG', 'false');
|
localStorage.setItem('ROARR_LOG', 'false');
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { createDraftSafeSelectorCreator, createSelectorCreator, lruMemoize } from '@reduxjs/toolkit';
|
||||||
createDraftSafeSelectorCreator,
|
|
||||||
createSelectorCreator,
|
|
||||||
lruMemoize,
|
|
||||||
} from '@reduxjs/toolkit';
|
|
||||||
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
import type { GetSelectorsOptions } from '@reduxjs/toolkit/dist/entities/state_selectors';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
|
@ -1,19 +1,12 @@
|
|||||||
import { StorageError } from 'app/store/enhancers/reduxRemember/errors';
|
import { StorageError } from 'app/store/enhancers/reduxRemember/errors';
|
||||||
import { $projectId } from 'app/store/nanostores/projectId';
|
import { $projectId } from 'app/store/nanostores/projectId';
|
||||||
import type { UseStore } from 'idb-keyval';
|
import type { UseStore } from 'idb-keyval';
|
||||||
import {
|
import { clear, createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
|
||||||
clear,
|
|
||||||
createStore as createIDBKeyValStore,
|
|
||||||
get,
|
|
||||||
set,
|
|
||||||
} from 'idb-keyval';
|
|
||||||
import { action, atom } from 'nanostores';
|
import { action, atom } from 'nanostores';
|
||||||
import type { Driver } from 'redux-remember';
|
import type { Driver } from 'redux-remember';
|
||||||
|
|
||||||
// Create a custom idb-keyval store (just needed to customize the name)
|
// Create a custom idb-keyval store (just needed to customize the name)
|
||||||
export const $idbKeyValStore = atom<UseStore>(
|
export const $idbKeyValStore = atom<UseStore>(createIDBKeyValStore('invoke', 'invoke-store'));
|
||||||
createIDBKeyValStore('invoke', 'invoke-store')
|
|
||||||
);
|
|
||||||
|
|
||||||
export const clearIdbKeyValStore = action($idbKeyValStore, 'clear', (store) => {
|
export const clearIdbKeyValStore = action($idbKeyValStore, 'clear', (store) => {
|
||||||
clear(store.get());
|
clear(store.get());
|
||||||
|
@ -4,8 +4,7 @@ import { diff } from 'jsondiffpatch';
|
|||||||
/**
|
/**
|
||||||
* Super simple logger middleware. Useful for debugging when the redux devtools are awkward.
|
* Super simple logger middleware. Useful for debugging when the redux devtools are awkward.
|
||||||
*/
|
*/
|
||||||
export const debugLoggerMiddleware: Middleware =
|
export const debugLoggerMiddleware: Middleware = (api: MiddlewareAPI) => (next) => (action) => {
|
||||||
(api: MiddlewareAPI) => (next) => (action) => {
|
|
||||||
const originalState = api.getState();
|
const originalState = api.getState();
|
||||||
console.log('REDUX: dispatching', action);
|
console.log('REDUX: dispatching', action);
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
|
@ -35,8 +35,7 @@ export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
|
|||||||
if (socketGeneratorProgress.match(action)) {
|
if (socketGeneratorProgress.match(action)) {
|
||||||
const sanitized = cloneDeep(action);
|
const sanitized = cloneDeep(action);
|
||||||
if (sanitized.payload.data.progress_image) {
|
if (sanitized.payload.data.progress_image) {
|
||||||
sanitized.payload.data.progress_image.dataURL =
|
sanitized.payload.data.progress_image.dataURL = '<Progress image omitted>';
|
||||||
'<Progress image omitted>';
|
|
||||||
}
|
}
|
||||||
return sanitized;
|
return sanitized;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
import type {
|
import type { ListenerEffect, TypedAddListener, TypedStartListening, UnknownAction } from '@reduxjs/toolkit';
|
||||||
ListenerEffect,
|
|
||||||
TypedAddListener,
|
|
||||||
TypedStartListening,
|
|
||||||
UnknownAction,
|
|
||||||
} from '@reduxjs/toolkit';
|
|
||||||
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
|
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||||
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
import { addGalleryImageClickedListener } from 'app/store/middleware/listenerMiddleware/listeners/galleryImageClicked';
|
||||||
import type { AppDispatch, RootState } from 'app/store/store';
|
import type { AppDispatch, RootState } from 'app/store/store';
|
||||||
@ -47,10 +42,7 @@ import {
|
|||||||
import { addImagesStarredListener } from './listeners/imagesStarred';
|
import { addImagesStarredListener } from './listeners/imagesStarred';
|
||||||
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
|
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
|
||||||
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
|
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
|
||||||
import {
|
import { addImageUploadedFulfilledListener, addImageUploadedRejectedListener } from './listeners/imageUploaded';
|
||||||
addImageUploadedFulfilledListener,
|
|
||||||
addImageUploadedRejectedListener,
|
|
||||||
} from './listeners/imageUploaded';
|
|
||||||
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
||||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||||
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
import { addModelsLoadedListener } from './listeners/modelsLoaded';
|
||||||
@ -78,19 +70,11 @@ export const listenerMiddleware = createListenerMiddleware();
|
|||||||
|
|
||||||
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
|
export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
|
||||||
|
|
||||||
export const startAppListening =
|
export const startAppListening = listenerMiddleware.startListening as AppStartListening;
|
||||||
listenerMiddleware.startListening as AppStartListening;
|
|
||||||
|
|
||||||
export const addAppListener = addListener as TypedAddListener<
|
export const addAppListener = addListener as TypedAddListener<RootState, AppDispatch>;
|
||||||
RootState,
|
|
||||||
AppDispatch
|
|
||||||
>;
|
|
||||||
|
|
||||||
export type AppListenerEffect = ListenerEffect<
|
export type AppListenerEffect = ListenerEffect<UnknownAction, RootState, AppDispatch>;
|
||||||
UnknownAction,
|
|
||||||
RootState,
|
|
||||||
AppDispatch
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The RTK listener middleware is a lightweight alternative sagas/observables.
|
* The RTK listener middleware is a lightweight alternative sagas/observables.
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { isAnyOf } from '@reduxjs/toolkit';
|
import { isAnyOf } from '@reduxjs/toolkit';
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import {
|
import { canvasBatchIdsReset, commitStagingAreaImage, discardStagedImages } from 'features/canvas/store/canvasSlice';
|
||||||
canvasBatchIdsReset,
|
|
||||||
commitStagingAreaImage,
|
|
||||||
discardStagedImages,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
@ -23,10 +19,7 @@ export const addCommitStagingAreaImageListener = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const req = dispatch(
|
const req = dispatch(
|
||||||
queueApi.endpoints.cancelByBatchIds.initiate(
|
queueApi.endpoints.cancelByBatchIds.initiate({ batch_ids: batchIds }, { fixedCacheKey: 'cancelByBatchIds' })
|
||||||
{ batch_ids: batchIds },
|
|
||||||
{ fixedCacheKey: 'cancelByBatchIds' }
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
const { canceled } = await req.unwrap();
|
const { canceled } = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
|
@ -12,15 +12,9 @@ export const appStarted = createAction('app/appStarted');
|
|||||||
export const addFirstListImagesListener = () => {
|
export const addFirstListImagesListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: imagesApi.endpoints.listImages.matchFulfilled,
|
matcher: imagesApi.endpoints.listImages.matchFulfilled,
|
||||||
effect: async (
|
effect: async (action, { dispatch, unsubscribe, cancelActiveListeners }) => {
|
||||||
action,
|
|
||||||
{ dispatch, unsubscribe, cancelActiveListeners }
|
|
||||||
) => {
|
|
||||||
// Only run this listener on the first listImages request for no-board images
|
// Only run this listener on the first listImages request for no-board images
|
||||||
if (
|
if (action.meta.arg.queryCacheKey !== getListImagesUrl({ board_id: 'none', categories: IMAGE_CATEGORIES })) {
|
||||||
action.meta.arg.queryCacheKey !==
|
|
||||||
getListImagesUrl({ board_id: 'none', categories: IMAGE_CATEGORIES })
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
import { setInfillMethod } from 'features/parameters/store/generationSlice';
|
||||||
import {
|
import { shouldUseNSFWCheckerChanged, shouldUseWatermarkerChanged } from 'features/system/store/systemSlice';
|
||||||
shouldUseNSFWCheckerChanged,
|
|
||||||
shouldUseWatermarkerChanged,
|
|
||||||
} from 'features/system/store/systemSlice';
|
|
||||||
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
import { appInfoApi } from 'services/api/endpoints/appInfo';
|
||||||
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
@ -11,11 +8,7 @@ export const addAppConfigReceivedListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
matcher: appInfoApi.endpoints.getAppConfig.matchFulfilled,
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const {
|
const { infill_methods = [], nsfw_methods = [], watermarking_methods = [] } = action.payload;
|
||||||
infill_methods = [],
|
|
||||||
nsfw_methods = [],
|
|
||||||
watermarking_methods = [],
|
|
||||||
} = action.payload;
|
|
||||||
const infillMethod = getState().generation.infillMethod;
|
const infillMethod = getState().generation.infillMethod;
|
||||||
|
|
||||||
if (!infill_methods.includes(infillMethod)) {
|
if (!infill_methods.includes(infillMethod)) {
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { createStandaloneToast, theme, TOAST_OPTIONS } from '@invoke-ai/ui-library';
|
||||||
createStandaloneToast,
|
|
||||||
theme,
|
|
||||||
TOAST_OPTIONS,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
import { zPydanticValidationError } from 'features/system/store/zodSchemas';
|
||||||
@ -24,10 +20,7 @@ export const addBatchEnqueuedListener = () => {
|
|||||||
effect: async (action) => {
|
effect: async (action) => {
|
||||||
const response = action.payload;
|
const response = action.payload;
|
||||||
const arg = action.meta.arg.originalArgs;
|
const arg = action.meta.arg.originalArgs;
|
||||||
logger('queue').debug(
|
logger('queue').debug({ enqueueResult: parseify(response) }, 'Batch enqueued');
|
||||||
{ enqueueResult: parseify(response) },
|
|
||||||
'Batch enqueued'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!toast.isActive('batch-queued')) {
|
if (!toast.isActive('batch-queued')) {
|
||||||
toast({
|
toast({
|
||||||
@ -57,10 +50,7 @@ export const addBatchEnqueuedListener = () => {
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
description: 'Unknown Error',
|
description: 'Unknown Error',
|
||||||
});
|
});
|
||||||
logger('queue').error(
|
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||||
{ batchConfig: parseify(arg), error: parseify(response) },
|
|
||||||
t('queue.batchFailedToQueue')
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +75,7 @@ export const addBatchEnqueuedListener = () => {
|
|||||||
status: 'error',
|
status: 'error',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
logger('queue').error(
|
logger('queue').error({ batchConfig: parseify(arg), error: parseify(response) }, t('queue.batchFailedToQueue'));
|
||||||
{ batchConfig: parseify(arg), error: parseify(response) },
|
|
||||||
t('queue.batchFailedToQueue')
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -22,13 +22,7 @@ export const addDeleteBoardAndImagesFulfilledListener = () => {
|
|||||||
|
|
||||||
const { generation, canvas, nodes, controlAdapters } = getState();
|
const { generation, canvas, nodes, controlAdapters } = getState();
|
||||||
deleted_images.forEach((image_name) => {
|
deleted_images.forEach((image_name) => {
|
||||||
const imageUsage = getImageUsage(
|
const imageUsage = getImageUsage(generation, canvas, nodes, controlAdapters, image_name);
|
||||||
generation,
|
|
||||||
canvas,
|
|
||||||
nodes,
|
|
||||||
controlAdapters,
|
|
||||||
image_name
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imageUsage.isInitialImage && !wasInitialImageReset) {
|
if (imageUsage.isInitialImage && !wasInitialImageReset) {
|
||||||
dispatch(clearInitialImage());
|
dispatch(clearInitialImage());
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { isAnyOf } from '@reduxjs/toolkit';
|
import { isAnyOf } from '@reduxjs/toolkit';
|
||||||
import {
|
import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
boardIdSelected,
|
import { ASSETS_CATEGORIES, IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
galleryViewChanged,
|
|
||||||
imageSelected,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import {
|
|
||||||
ASSETS_CATEGORIES,
|
|
||||||
IMAGE_CATEGORIES,
|
|
||||||
} from 'features/gallery/store/types';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import { imagesSelectors } from 'services/api/util';
|
import { imagesSelectors } from 'services/api/util';
|
||||||
|
|
||||||
@ -16,53 +9,35 @@ import { startAppListening } from '..';
|
|||||||
export const addBoardIdSelectedListener = () => {
|
export const addBoardIdSelectedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher: isAnyOf(boardIdSelected, galleryViewChanged),
|
matcher: isAnyOf(boardIdSelected, galleryViewChanged),
|
||||||
effect: async (
|
effect: async (action, { getState, dispatch, condition, cancelActiveListeners }) => {
|
||||||
action,
|
|
||||||
{ getState, dispatch, condition, cancelActiveListeners }
|
|
||||||
) => {
|
|
||||||
// Cancel any in-progress instances of this listener, we don't want to select an image from a previous board
|
// Cancel any in-progress instances of this listener, we don't want to select an image from a previous board
|
||||||
cancelActiveListeners();
|
cancelActiveListeners();
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const board_id = boardIdSelected.match(action)
|
const board_id = boardIdSelected.match(action) ? action.payload.boardId : state.gallery.selectedBoardId;
|
||||||
? action.payload.boardId
|
|
||||||
: state.gallery.selectedBoardId;
|
|
||||||
|
|
||||||
const galleryView = galleryViewChanged.match(action)
|
const galleryView = galleryViewChanged.match(action) ? action.payload : state.gallery.galleryView;
|
||||||
? action.payload
|
|
||||||
: state.gallery.galleryView;
|
|
||||||
|
|
||||||
// when a board is selected, we need to wait until the board has loaded *some* images, then select the first one
|
// when a board is selected, we need to wait until the board has loaded *some* images, then select the first one
|
||||||
const categories =
|
const categories = galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
|
||||||
galleryView === 'images' ? IMAGE_CATEGORIES : ASSETS_CATEGORIES;
|
|
||||||
|
|
||||||
const queryArgs = { board_id: board_id ?? 'none', categories };
|
const queryArgs = { board_id: board_id ?? 'none', categories };
|
||||||
|
|
||||||
// wait until the board has some images - maybe it already has some from a previous fetch
|
// wait until the board has some images - maybe it already has some from a previous fetch
|
||||||
// must use getState() to ensure we do not have stale state
|
// must use getState() to ensure we do not have stale state
|
||||||
const isSuccess = await condition(
|
const isSuccess = await condition(
|
||||||
() =>
|
() => imagesApi.endpoints.listImages.select(queryArgs)(getState()).isSuccess,
|
||||||
imagesApi.endpoints.listImages.select(queryArgs)(getState())
|
|
||||||
.isSuccess,
|
|
||||||
5000
|
5000
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
// the board was just changed - we can select the first image
|
// the board was just changed - we can select the first image
|
||||||
const { data: boardImagesData } =
|
const { data: boardImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(getState());
|
||||||
imagesApi.endpoints.listImages.select(queryArgs)(getState());
|
|
||||||
|
|
||||||
if (
|
if (boardImagesData && boardIdSelected.match(action) && action.payload.selectedImageName) {
|
||||||
boardImagesData &&
|
|
||||||
boardIdSelected.match(action) &&
|
|
||||||
action.payload.selectedImageName
|
|
||||||
) {
|
|
||||||
const firstImage = imagesSelectors.selectAll(boardImagesData)[0];
|
const firstImage = imagesSelectors.selectAll(boardImagesData)[0];
|
||||||
const selectedImage = imagesSelectors.selectById(
|
const selectedImage = imagesSelectors.selectById(boardImagesData, action.payload.selectedImageName);
|
||||||
boardImagesData,
|
|
||||||
action.payload.selectedImageName
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(imageSelected(selectedImage || firstImage || null));
|
dispatch(imageSelected(selectedImage || firstImage || null));
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,9 +11,7 @@ export const addCanvasCopiedToClipboardListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: canvasCopiedToClipboard,
|
actionCreator: canvasCopiedToClipboard,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const moduleLog = $logger
|
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||||
.get()
|
|
||||||
.child({ namespace: 'canvasCopiedToClipboardListener' });
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -11,9 +11,7 @@ export const addCanvasDownloadedAsImageListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: canvasDownloadedAsImage,
|
actionCreator: canvasDownloadedAsImage,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const moduleLog = $logger
|
const moduleLog = $logger.get().child({ namespace: 'canvasSavedToGalleryListener' });
|
||||||
.get()
|
|
||||||
.child({ namespace: 'canvasSavedToGalleryListener' });
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
let blob;
|
let blob;
|
||||||
@ -32,9 +30,7 @@ export const addCanvasDownloadedAsImageListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadBlob(blob, 'canvas.png');
|
downloadBlob(blob, 'canvas.png');
|
||||||
dispatch(
|
dispatch(addToast({ title: t('toast.canvasDownloaded'), status: 'success' }));
|
||||||
addToast({ title: t('toast.canvasDownloaded'), status: 'success' })
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -13,9 +13,7 @@ export const addCanvasMergedListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: canvasMerged,
|
actionCreator: canvasMerged,
|
||||||
effect: async (action, { dispatch }) => {
|
effect: async (action, { dispatch }) => {
|
||||||
const moduleLog = $logger
|
const moduleLog = $logger.get().child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||||
.get()
|
|
||||||
.child({ namespace: 'canvasCopiedToClipboardListener' });
|
|
||||||
const blob = await getFullBaseLayerBlob();
|
const blob = await getFullBaseLayerBlob();
|
||||||
|
|
||||||
if (!blob) {
|
if (!blob) {
|
||||||
|
@ -21,11 +21,7 @@ type AnyControlAdapterParamChangeAction =
|
|||||||
| ReturnType<typeof controlAdapterProcessortTypeChanged>
|
| ReturnType<typeof controlAdapterProcessortTypeChanged>
|
||||||
| ReturnType<typeof controlAdapterAutoConfigToggled>;
|
| ReturnType<typeof controlAdapterAutoConfigToggled>;
|
||||||
|
|
||||||
const predicate: AnyListenerPredicate<RootState> = (
|
const predicate: AnyListenerPredicate<RootState> = (action, state, prevState) => {
|
||||||
action,
|
|
||||||
state,
|
|
||||||
prevState
|
|
||||||
) => {
|
|
||||||
const isActionMatched =
|
const isActionMatched =
|
||||||
controlAdapterProcessorParamsChanged.match(action) ||
|
controlAdapterProcessorParamsChanged.match(action) ||
|
||||||
controlAdapterModelChanged.match(action) ||
|
controlAdapterModelChanged.match(action) ||
|
||||||
@ -40,12 +36,7 @@ const predicate: AnyListenerPredicate<RootState> = (
|
|||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
const prevCA = selectControlAdapterById(prevState.controlAdapters, id);
|
const prevCA = selectControlAdapterById(prevState.controlAdapters, id);
|
||||||
const ca = selectControlAdapterById(state.controlAdapters, id);
|
const ca = selectControlAdapterById(state.controlAdapters, id);
|
||||||
if (
|
if (!prevCA || !isControlNetOrT2IAdapter(prevCA) || !ca || !isControlNetOrT2IAdapter(ca)) {
|
||||||
!prevCA ||
|
|
||||||
!isControlNetOrT2IAdapter(prevCA) ||
|
|
||||||
!ca ||
|
|
||||||
!isControlNetOrT2IAdapter(ca)
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,37 +64,28 @@ export const addControlNetImageProcessedListener = () => {
|
|||||||
);
|
);
|
||||||
const enqueueResult = await req.unwrap();
|
const enqueueResult = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
log.debug(
|
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||||
{ enqueueResult: parseify(enqueueResult) },
|
|
||||||
t('queue.graphQueued')
|
|
||||||
);
|
|
||||||
|
|
||||||
const [invocationCompleteAction] = await take(
|
const [invocationCompleteAction] = await take(
|
||||||
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
(action): action is ReturnType<typeof socketInvocationComplete> =>
|
||||||
socketInvocationComplete.match(action) &&
|
socketInvocationComplete.match(action) &&
|
||||||
action.payload.data.queue_batch_id ===
|
action.payload.data.queue_batch_id === enqueueResult.batch.batch_id &&
|
||||||
enqueueResult.batch.batch_id &&
|
|
||||||
action.payload.data.source_node_id === nodeId
|
action.payload.data.source_node_id === nodeId
|
||||||
);
|
);
|
||||||
|
|
||||||
// We still have to check the output type
|
// We still have to check the output type
|
||||||
if (isImageOutput(invocationCompleteAction.payload.data.result)) {
|
if (isImageOutput(invocationCompleteAction.payload.data.result)) {
|
||||||
const { image_name } =
|
const { image_name } = invocationCompleteAction.payload.data.result.image;
|
||||||
invocationCompleteAction.payload.data.result.image;
|
|
||||||
|
|
||||||
// Wait for the ImageDTO to be received
|
// Wait for the ImageDTO to be received
|
||||||
const [{ payload }] = await take(
|
const [{ payload }] = await take(
|
||||||
(action) =>
|
(action) =>
|
||||||
imagesApi.endpoints.getImageDTO.matchFulfilled(action) &&
|
imagesApi.endpoints.getImageDTO.matchFulfilled(action) && action.payload.image_name === image_name
|
||||||
action.payload.image_name === image_name
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const processedControlImage = payload as ImageDTO;
|
const processedControlImage = payload as ImageDTO;
|
||||||
|
|
||||||
log.debug(
|
log.debug({ controlNetId: action.payload, processedControlImage }, 'ControlNet image processed');
|
||||||
{ controlNetId: action.payload, processedControlImage },
|
|
||||||
'ControlNet image processed'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update the processed image in the store
|
// Update the processed image in the store
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -105,10 +96,7 @@ export const addControlNetImageProcessedListener = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||||
{ enqueueBatchArg: parseify(enqueueBatchArg) },
|
|
||||||
t('queue.graphFailedToQueue')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error instanceof Object) {
|
if (error instanceof Object) {
|
||||||
if ('data' in error && 'status' in error) {
|
if ('data' in error && 'status' in error) {
|
||||||
|
@ -2,10 +2,7 @@ import { logger } from 'app/logging/logger';
|
|||||||
import { enqueueRequested } from 'app/store/actions';
|
import { enqueueRequested } from 'app/store/actions';
|
||||||
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import {
|
import { canvasBatchIdAdded, stagingAreaInitialized } from 'features/canvas/store/canvasSlice';
|
||||||
canvasBatchIdAdded,
|
|
||||||
stagingAreaInitialized,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
|
||||||
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
import { getCanvasData } from 'features/canvas/util/getCanvasData';
|
||||||
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
|
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
|
||||||
@ -34,20 +31,14 @@ import { startAppListening } from '..';
|
|||||||
export const addEnqueueRequestedCanvasListener = () => {
|
export const addEnqueueRequestedCanvasListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||||
enqueueRequested.match(action) &&
|
enqueueRequested.match(action) && action.payload.tabName === 'unifiedCanvas',
|
||||||
action.payload.tabName === 'unifiedCanvas',
|
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const log = logger('queue');
|
const log = logger('queue');
|
||||||
const { prepend } = action.payload;
|
const { prepend } = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const {
|
const { layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea } =
|
||||||
layerState,
|
state.canvas;
|
||||||
boundingBoxCoordinates,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
isMaskEnabled,
|
|
||||||
shouldPreserveMaskedArea,
|
|
||||||
} = state.canvas;
|
|
||||||
|
|
||||||
// Build canvas blobs
|
// Build canvas blobs
|
||||||
const canvasBlobsAndImageData = await getCanvasData(
|
const canvasBlobsAndImageData = await getCanvasData(
|
||||||
@ -63,14 +54,10 @@ export const addEnqueueRequestedCanvasListener = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { baseBlob, baseImageData, maskBlob, maskImageData } =
|
const { baseBlob, baseImageData, maskBlob, maskImageData } = canvasBlobsAndImageData;
|
||||||
canvasBlobsAndImageData;
|
|
||||||
|
|
||||||
// Determine the generation mode
|
// Determine the generation mode
|
||||||
const generationMode = getCanvasGenerationMode(
|
const generationMode = getCanvasGenerationMode(baseImageData, maskImageData);
|
||||||
baseImageData,
|
|
||||||
maskImageData
|
|
||||||
);
|
|
||||||
|
|
||||||
if (state.system.enableImageDebugging) {
|
if (state.system.enableImageDebugging) {
|
||||||
const baseDataURL = await blobToDataURL(baseBlob);
|
const baseDataURL = await blobToDataURL(baseBlob);
|
||||||
@ -115,12 +102,7 @@ export const addEnqueueRequestedCanvasListener = () => {
|
|||||||
).unwrap();
|
).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
const graph = buildCanvasGraph(
|
const graph = buildCanvasGraph(state, generationMode, canvasInitImage, canvasMaskImage);
|
||||||
state,
|
|
||||||
generationMode,
|
|
||||||
canvasInitImage,
|
|
||||||
canvasMaskImage
|
|
||||||
);
|
|
||||||
|
|
||||||
log.debug({ graph: parseify(graph) }, `Canvas graph built`);
|
log.debug({ graph: parseify(graph) }, `Canvas graph built`);
|
||||||
|
|
||||||
|
@ -11,9 +11,7 @@ import { startAppListening } from '..';
|
|||||||
export const addEnqueueRequestedLinear = () => {
|
export const addEnqueueRequestedLinear = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
predicate: (action): action is ReturnType<typeof enqueueRequested> =>
|
||||||
enqueueRequested.match(action) &&
|
enqueueRequested.match(action) && (action.payload.tabName === 'txt2img' || action.payload.tabName === 'img2img'),
|
||||||
(action.payload.tabName === 'txt2img' ||
|
|
||||||
action.payload.tabName === 'img2img'),
|
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const model = state.generation.model;
|
const model = state.generation.model;
|
||||||
|
@ -32,8 +32,7 @@ export const addGalleryImageClickedListener = () => {
|
|||||||
const { imageDTO, shiftKey, ctrlKey, metaKey } = action.payload;
|
const { imageDTO, shiftKey, ctrlKey, metaKey } = action.payload;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const queryArgs = selectListImagesQueryArgs(state);
|
const queryArgs = selectListImagesQueryArgs(state);
|
||||||
const { data: listImagesData } =
|
const { data: listImagesData } = imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||||
imagesApi.endpoints.listImages.select(queryArgs)(state);
|
|
||||||
|
|
||||||
if (!listImagesData) {
|
if (!listImagesData) {
|
||||||
// Should never happen if we have clicked a gallery image
|
// Should never happen if we have clicked a gallery image
|
||||||
@ -46,12 +45,8 @@ export const addGalleryImageClickedListener = () => {
|
|||||||
if (shiftKey) {
|
if (shiftKey) {
|
||||||
const rangeEndImageName = imageDTO.image_name;
|
const rangeEndImageName = imageDTO.image_name;
|
||||||
const lastSelectedImage = selection[selection.length - 1]?.image_name;
|
const lastSelectedImage = selection[selection.length - 1]?.image_name;
|
||||||
const lastClickedIndex = imageDTOs.findIndex(
|
const lastClickedIndex = imageDTOs.findIndex((n) => n.image_name === lastSelectedImage);
|
||||||
(n) => n.image_name === lastSelectedImage
|
const currentClickedIndex = imageDTOs.findIndex((n) => n.image_name === rangeEndImageName);
|
||||||
);
|
|
||||||
const currentClickedIndex = imageDTOs.findIndex(
|
|
||||||
(n) => n.image_name === rangeEndImageName
|
|
||||||
);
|
|
||||||
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
||||||
// We have a valid range!
|
// We have a valid range!
|
||||||
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
||||||
@ -60,15 +55,8 @@ export const addGalleryImageClickedListener = () => {
|
|||||||
dispatch(selectionChanged(selection.concat(imagesToSelect)));
|
dispatch(selectionChanged(selection.concat(imagesToSelect)));
|
||||||
}
|
}
|
||||||
} else if (ctrlKey || metaKey) {
|
} else if (ctrlKey || metaKey) {
|
||||||
if (
|
if (selection.some((i) => i.image_name === imageDTO.image_name) && selection.length > 1) {
|
||||||
selection.some((i) => i.image_name === imageDTO.image_name) &&
|
dispatch(selectionChanged(selection.filter((n) => n.image_name !== imageDTO.image_name)));
|
||||||
selection.length > 1
|
|
||||||
) {
|
|
||||||
dispatch(
|
|
||||||
selectionChanged(
|
|
||||||
selection.filter((n) => n.image_name !== imageDTO.image_name)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(selectionChanged(selection.concat(imageDTO)));
|
dispatch(selectionChanged(selection.concat(imageDTO)));
|
||||||
}
|
}
|
||||||
|
@ -43,31 +43,21 @@ export const addRequestedSingleImageDeletionListener = () => {
|
|||||||
dispatch(isModalOpenChanged(false));
|
dispatch(isModalOpenChanged(false));
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const lastSelectedImage =
|
const lastSelectedImage = state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
|
||||||
state.gallery.selection[state.gallery.selection.length - 1]?.image_name;
|
|
||||||
|
|
||||||
if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
|
if (imageDTO && imageDTO?.image_name === lastSelectedImage) {
|
||||||
const { image_name } = imageDTO;
|
const { image_name } = imageDTO;
|
||||||
|
|
||||||
const baseQueryArgs = selectListImagesQueryArgs(state);
|
const baseQueryArgs = selectListImagesQueryArgs(state);
|
||||||
const { data } =
|
const { data } = imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
|
||||||
imagesApi.endpoints.listImages.select(baseQueryArgs)(state);
|
|
||||||
|
|
||||||
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
|
const cachedImageDTOs = data ? imagesSelectors.selectAll(data) : [];
|
||||||
|
|
||||||
const deletedImageIndex = cachedImageDTOs.findIndex(
|
const deletedImageIndex = cachedImageDTOs.findIndex((i) => i.image_name === image_name);
|
||||||
(i) => i.image_name === image_name
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredImageDTOs = cachedImageDTOs.filter(
|
const filteredImageDTOs = cachedImageDTOs.filter((i) => i.image_name !== image_name);
|
||||||
(i) => i.image_name !== image_name
|
|
||||||
);
|
|
||||||
|
|
||||||
const newSelectedImageIndex = clamp(
|
const newSelectedImageIndex = clamp(deletedImageIndex, 0, filteredImageDTOs.length - 1);
|
||||||
deletedImageIndex,
|
|
||||||
0,
|
|
||||||
filteredImageDTOs.length - 1
|
|
||||||
);
|
|
||||||
|
|
||||||
const newSelectedImageDTO = filteredImageDTOs[newSelectedImageIndex];
|
const newSelectedImageDTO = filteredImageDTOs[newSelectedImageIndex];
|
||||||
|
|
||||||
@ -85,9 +75,7 @@ export const addRequestedSingleImageDeletionListener = () => {
|
|||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
// reset init image if we deleted it
|
// reset init image if we deleted it
|
||||||
if (
|
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
||||||
getState().generation.initialImage?.imageName === imageDTO.image_name
|
|
||||||
) {
|
|
||||||
dispatch(clearInitialImage());
|
dispatch(clearInitialImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +83,7 @@ export const addRequestedSingleImageDeletionListener = () => {
|
|||||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
||||||
if (
|
if (
|
||||||
ca.controlImage === imageDTO.image_name ||
|
ca.controlImage === imageDTO.image_name ||
|
||||||
(isControlNetOrT2IAdapter(ca) &&
|
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||||
ca.processedControlImage === imageDTO.image_name)
|
|
||||||
) {
|
) {
|
||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterImageChanged({
|
controlAdapterImageChanged({
|
||||||
@ -120,10 +107,7 @@ export const addRequestedSingleImageDeletionListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
forEach(node.data.inputs, (input) => {
|
forEach(node.data.inputs, (input) => {
|
||||||
if (
|
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||||
isImageFieldInputInstance(input) &&
|
|
||||||
input.value?.image_name === imageDTO.image_name
|
|
||||||
) {
|
|
||||||
dispatch(
|
dispatch(
|
||||||
fieldImageValueChanged({
|
fieldImageValueChanged({
|
||||||
nodeId: node.data.id,
|
nodeId: node.data.id,
|
||||||
@ -137,24 +121,16 @@ export const addRequestedSingleImageDeletionListener = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Delete from server
|
// Delete from server
|
||||||
const { requestId } = dispatch(
|
const { requestId } = dispatch(imagesApi.endpoints.deleteImage.initiate(imageDTO));
|
||||||
imagesApi.endpoints.deleteImage.initiate(imageDTO)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait for successful deletion, then trigger boards to re-fetch
|
// Wait for successful deletion, then trigger boards to re-fetch
|
||||||
const wasImageDeleted = await condition(
|
const wasImageDeleted = await condition(
|
||||||
(action) =>
|
(action) => imagesApi.endpoints.deleteImage.matchFulfilled(action) && action.meta.requestId === requestId,
|
||||||
imagesApi.endpoints.deleteImage.matchFulfilled(action) &&
|
|
||||||
action.meta.requestId === requestId,
|
|
||||||
30000
|
30000
|
||||||
);
|
);
|
||||||
|
|
||||||
if (wasImageDeleted) {
|
if (wasImageDeleted) {
|
||||||
dispatch(
|
dispatch(api.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }]));
|
||||||
api.util.invalidateTags([
|
|
||||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -176,17 +152,12 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete from server
|
// Delete from server
|
||||||
await dispatch(
|
await dispatch(imagesApi.endpoints.deleteImages.initiate({ imageDTOs })).unwrap();
|
||||||
imagesApi.endpoints.deleteImages.initiate({ imageDTOs })
|
|
||||||
).unwrap();
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const queryArgs = selectListImagesQueryArgs(state);
|
const queryArgs = selectListImagesQueryArgs(state);
|
||||||
const { data } =
|
const { data } = imagesApi.endpoints.listImages.select(queryArgs)(state);
|
||||||
imagesApi.endpoints.listImages.select(queryArgs)(state);
|
|
||||||
|
|
||||||
const newSelectedImageDTO = data
|
const newSelectedImageDTO = data ? imagesSelectors.selectAll(data)[0] : undefined;
|
||||||
? imagesSelectors.selectAll(data)[0]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (newSelectedImageDTO) {
|
if (newSelectedImageDTO) {
|
||||||
dispatch(imageSelected(newSelectedImageDTO));
|
dispatch(imageSelected(newSelectedImageDTO));
|
||||||
@ -204,10 +175,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
|||||||
|
|
||||||
imageDTOs.forEach((imageDTO) => {
|
imageDTOs.forEach((imageDTO) => {
|
||||||
// reset init image if we deleted it
|
// reset init image if we deleted it
|
||||||
if (
|
if (getState().generation.initialImage?.imageName === imageDTO.image_name) {
|
||||||
getState().generation.initialImage?.imageName ===
|
|
||||||
imageDTO.image_name
|
|
||||||
) {
|
|
||||||
dispatch(clearInitialImage());
|
dispatch(clearInitialImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,8 +183,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
|||||||
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
forEach(selectControlAdapterAll(getState().controlAdapters), (ca) => {
|
||||||
if (
|
if (
|
||||||
ca.controlImage === imageDTO.image_name ||
|
ca.controlImage === imageDTO.image_name ||
|
||||||
(isControlNetOrT2IAdapter(ca) &&
|
(isControlNetOrT2IAdapter(ca) && ca.processedControlImage === imageDTO.image_name)
|
||||||
ca.processedControlImage === imageDTO.image_name)
|
|
||||||
) {
|
) {
|
||||||
dispatch(
|
dispatch(
|
||||||
controlAdapterImageChanged({
|
controlAdapterImageChanged({
|
||||||
@ -240,10 +207,7 @@ export const addRequestedMultipleImageDeletionListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
forEach(node.data.inputs, (input) => {
|
forEach(node.data.inputs, (input) => {
|
||||||
if (
|
if (isImageFieldInputInstance(input) && input.value?.image_name === imageDTO.image_name) {
|
||||||
isImageFieldInputInstance(input) &&
|
|
||||||
input.value?.image_name === imageDTO.image_name
|
|
||||||
) {
|
|
||||||
dispatch(
|
dispatch(
|
||||||
fieldImageValueChanged({
|
fieldImageValueChanged({
|
||||||
nodeId: node.data.id,
|
nodeId: node.data.id,
|
||||||
@ -295,10 +259,7 @@ export const addImageDeletedRejectedListener = () => {
|
|||||||
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
matcher: imagesApi.endpoints.deleteImage.matchRejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('images');
|
const log = logger('images');
|
||||||
log.debug(
|
log.debug({ imageDTO: action.meta.arg.originalArgs }, 'Unable to delete image');
|
||||||
{ imageDTO: action.meta.arg.originalArgs },
|
|
||||||
'Unable to delete image'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -6,16 +6,10 @@ import {
|
|||||||
controlAdapterImageChanged,
|
controlAdapterImageChanged,
|
||||||
controlAdapterIsEnabledChanged,
|
controlAdapterIsEnabledChanged,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import type {
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
TypesafeDraggableData,
|
|
||||||
TypesafeDroppableData,
|
|
||||||
} from 'features/dnd/types';
|
|
||||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import {
|
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
initialImageChanged,
|
|
||||||
selectOptimalDimension,
|
|
||||||
} from 'features/parameters/store/generationSlice';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
import { startAppListening } from '../';
|
import { startAppListening } from '../';
|
||||||
@ -35,15 +29,9 @@ export const addImageDroppedListener = () => {
|
|||||||
if (activeData.payloadType === 'IMAGE_DTO') {
|
if (activeData.payloadType === 'IMAGE_DTO') {
|
||||||
log.debug({ activeData, overData }, 'Image dropped');
|
log.debug({ activeData, overData }, 'Image dropped');
|
||||||
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
} else if (activeData.payloadType === 'GALLERY_SELECTION') {
|
||||||
log.debug(
|
log.debug({ activeData, overData }, `Images (${getState().gallery.selection.length}) dropped`);
|
||||||
{ activeData, overData },
|
|
||||||
`Images (${getState().gallery.selection.length}) dropped`
|
|
||||||
);
|
|
||||||
} else if (activeData.payloadType === 'NODE_FIELD') {
|
} else if (activeData.payloadType === 'NODE_FIELD') {
|
||||||
log.debug(
|
log.debug({ activeData: parseify(activeData), overData: parseify(overData) }, 'Node field dropped');
|
||||||
{ activeData: parseify(activeData), overData: parseify(overData) },
|
|
||||||
'Node field dropped'
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
log.debug({ activeData, overData }, `Unknown payload dropped`);
|
||||||
}
|
}
|
||||||
@ -104,12 +92,7 @@ export const addImageDroppedListener = () => {
|
|||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(
|
dispatch(setInitialCanvasImage(activeData.payload.imageDTO, selectOptimalDimension(getState())));
|
||||||
setInitialCanvasImage(
|
|
||||||
activeData.payload.imageDTO,
|
|
||||||
selectOptimalDimension(getState())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,10 +174,7 @@ export const addImageDroppedListener = () => {
|
|||||||
/**
|
/**
|
||||||
* Multiple images dropped on user board
|
* Multiple images dropped on user board
|
||||||
*/
|
*/
|
||||||
if (
|
if (overData.actionType === 'ADD_TO_BOARD' && activeData.payloadType === 'GALLERY_SELECTION') {
|
||||||
overData.actionType === 'ADD_TO_BOARD' &&
|
|
||||||
activeData.payloadType === 'GALLERY_SELECTION'
|
|
||||||
) {
|
|
||||||
const imageDTOs = getState().gallery.selection;
|
const imageDTOs = getState().gallery.selection;
|
||||||
const { boardId } = overData.context;
|
const { boardId } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -209,10 +189,7 @@ export const addImageDroppedListener = () => {
|
|||||||
/**
|
/**
|
||||||
* Multiple images dropped on 'none' board
|
* Multiple images dropped on 'none' board
|
||||||
*/
|
*/
|
||||||
if (
|
if (overData.actionType === 'REMOVE_FROM_BOARD' && activeData.payloadType === 'GALLERY_SELECTION') {
|
||||||
overData.actionType === 'REMOVE_FROM_BOARD' &&
|
|
||||||
activeData.payloadType === 'GALLERY_SELECTION'
|
|
||||||
) {
|
|
||||||
const imageDTOs = getState().gallery.selection;
|
const imageDTOs = getState().gallery.selection;
|
||||||
dispatch(
|
dispatch(
|
||||||
imagesApi.endpoints.removeImagesFromBoard.initiate({
|
imagesApi.endpoints.removeImagesFromBoard.initiate({
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
import { imageDeletionConfirmed } from 'features/deleteImageModal/store/actions';
|
||||||
import { selectImageUsage } from 'features/deleteImageModal/store/selectors';
|
import { selectImageUsage } from 'features/deleteImageModal/store/selectors';
|
||||||
import {
|
import { imagesToDeleteSelected, isModalOpenChanged } from 'features/deleteImageModal/store/slice';
|
||||||
imagesToDeleteSelected,
|
|
||||||
isModalOpenChanged,
|
|
||||||
} from 'features/deleteImageModal/store/slice';
|
|
||||||
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
|
@ -6,10 +6,7 @@ import {
|
|||||||
controlAdapterIsEnabledChanged,
|
controlAdapterIsEnabledChanged,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import {
|
import { initialImageChanged, selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
initialImageChanged,
|
|
||||||
selectOptimalDimension,
|
|
||||||
} from 'features/parameters/store/generationSlice';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { omit } from 'lodash-es';
|
import { omit } from 'lodash-es';
|
||||||
@ -79,9 +76,7 @@ export const addImageUploadedFulfilledListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
|
||||||
dispatch(
|
dispatch(setInitialCanvasImage(imageDTO, selectOptimalDimension(state)));
|
||||||
setInitialCanvasImage(imageDTO, selectOptimalDimension(state))
|
|
||||||
);
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
@ -127,9 +122,7 @@ export const addImageUploadedFulfilledListener = () => {
|
|||||||
|
|
||||||
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
if (postUploadAction?.type === 'SET_NODES_IMAGE') {
|
||||||
const { nodeId, fieldName } = postUploadAction;
|
const { nodeId, fieldName } = postUploadAction;
|
||||||
dispatch(
|
dispatch(fieldImageValueChanged({ nodeId, fieldName, value: imageDTO }));
|
||||||
fieldImageValueChanged({ nodeId, fieldName, value: imageDTO })
|
|
||||||
);
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
...DEFAULT_UPLOADED_TOAST,
|
...DEFAULT_UPLOADED_TOAST,
|
||||||
|
@ -11,11 +11,7 @@ export const addInitialImageSelectedListener = () => {
|
|||||||
actionCreator: initialImageSelected,
|
actionCreator: initialImageSelected,
|
||||||
effect: (action, { dispatch }) => {
|
effect: (action, { dispatch }) => {
|
||||||
if (!action.payload) {
|
if (!action.payload) {
|
||||||
dispatch(
|
dispatch(addToast(makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })));
|
||||||
addToast(
|
|
||||||
makeToast({ title: t('toast.imageNotLoadedDesc'), status: 'error' })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,7 @@ import {
|
|||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||||
import { modelSelected } from 'features/parameters/store/actions';
|
import { modelSelected } from 'features/parameters/store/actions';
|
||||||
import {
|
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||||
modelChanged,
|
|
||||||
vaeSelected,
|
|
||||||
} from 'features/parameters/store/generationSlice';
|
|
||||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
@ -27,18 +24,14 @@ export const addModelSelectedListener = () => {
|
|||||||
const result = zParameterModel.safeParse(action.payload);
|
const result = zParameterModel.safeParse(action.payload);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
log.error(
|
log.error({ error: result.error.format() }, 'Failed to parse main model');
|
||||||
{ error: result.error.format() },
|
|
||||||
'Failed to parse main model'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newModel = result.data;
|
const newModel = result.data;
|
||||||
|
|
||||||
const newBaseModel = newModel.base_model;
|
const newBaseModel = newModel.base_model;
|
||||||
const didBaseModelChange =
|
const didBaseModelChange = state.generation.model?.base_model !== newBaseModel;
|
||||||
state.generation.model?.base_model !== newBaseModel;
|
|
||||||
|
|
||||||
if (didBaseModelChange) {
|
if (didBaseModelChange) {
|
||||||
// we may need to reset some incompatible submodels
|
// we may need to reset some incompatible submodels
|
||||||
@ -62,9 +55,7 @@ export const addModelSelectedListener = () => {
|
|||||||
// handle incompatible controlnets
|
// handle incompatible controlnets
|
||||||
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
selectControlAdapterAll(state.controlAdapters).forEach((ca) => {
|
||||||
if (ca.model?.base_model !== newBaseModel) {
|
if (ca.model?.base_model !== newBaseModel) {
|
||||||
dispatch(
|
dispatch(controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false }));
|
||||||
controlAdapterIsEnabledChanged({ id: ca.id, isEnabled: false })
|
|
||||||
);
|
|
||||||
modelsCleared += 1;
|
modelsCleared += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -6,41 +6,24 @@ import {
|
|||||||
selectAllT2IAdapters,
|
selectAllT2IAdapters,
|
||||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||||
import {
|
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||||
modelChanged,
|
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||||
vaeSelected,
|
|
||||||
} from 'features/parameters/store/generationSlice';
|
|
||||||
import {
|
|
||||||
zParameterModel,
|
|
||||||
zParameterVAEModel,
|
|
||||||
} from 'features/parameters/types/parameterSchemas';
|
|
||||||
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
||||||
import { forEach, some } from 'lodash-es';
|
import { forEach, some } from 'lodash-es';
|
||||||
import {
|
import { mainModelsAdapterSelectors, modelsApi, vaeModelsAdapterSelectors } from 'services/api/endpoints/models';
|
||||||
mainModelsAdapterSelectors,
|
|
||||||
modelsApi,
|
|
||||||
vaeModelsAdapterSelectors,
|
|
||||||
} from 'services/api/endpoints/models';
|
|
||||||
import type { TypeGuardFor } from 'services/api/types';
|
import type { TypeGuardFor } from 'services/api/types';
|
||||||
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
export const addModelsLoadedListener = () => {
|
export const addModelsLoadedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (
|
predicate: (action): action is TypeGuardFor<typeof modelsApi.endpoints.getMainModels.matchFulfilled> =>
|
||||||
action
|
|
||||||
): action is TypeGuardFor<
|
|
||||||
typeof modelsApi.endpoints.getMainModels.matchFulfilled
|
|
||||||
> =>
|
|
||||||
modelsApi.endpoints.getMainModels.matchFulfilled(action) &&
|
modelsApi.endpoints.getMainModels.matchFulfilled(action) &&
|
||||||
!action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
!action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `Main models loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`Main models loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentModel = getState().generation.model;
|
const currentModel = getState().generation.model;
|
||||||
const models = mainModelsAdapterSelectors.selectAll(action.payload);
|
const models = mainModelsAdapterSelectors.selectAll(action.payload);
|
||||||
@ -67,10 +50,7 @@ export const addModelsLoadedListener = () => {
|
|||||||
const result = zParameterModel.safeParse(models[0]);
|
const result = zParameterModel.safeParse(models[0]);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
log.error(
|
log.error({ error: result.error.format() }, 'Failed to parse main model');
|
||||||
{ error: result.error.format() },
|
|
||||||
'Failed to parse main model'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,20 +58,12 @@ export const addModelsLoadedListener = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
startAppListening({
|
startAppListening({
|
||||||
predicate: (
|
predicate: (action): action is TypeGuardFor<typeof modelsApi.endpoints.getMainModels.matchFulfilled> =>
|
||||||
action
|
modelsApi.endpoints.getMainModels.matchFulfilled(action) && action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
||||||
): action is TypeGuardFor<
|
|
||||||
typeof modelsApi.endpoints.getMainModels.matchFulfilled
|
|
||||||
> =>
|
|
||||||
modelsApi.endpoints.getMainModels.matchFulfilled(action) &&
|
|
||||||
action.meta.arg.originalArgs.includes('sdxl-refiner'),
|
|
||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
// models loaded, we need to ensure the selected model is available and if not, select the first one
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `SDXL Refiner models loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`SDXL Refiner models loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentModel = getState().sdxl.refinerModel;
|
const currentModel = getState().sdxl.refinerModel;
|
||||||
const models = mainModelsAdapterSelectors.selectAll(action.payload);
|
const models = mainModelsAdapterSelectors.selectAll(action.payload);
|
||||||
@ -122,10 +94,7 @@ export const addModelsLoadedListener = () => {
|
|||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// VAEs loaded, need to reset the VAE is it's no longer available
|
// VAEs loaded, need to reset the VAE is it's no longer available
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `VAEs loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`VAEs loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentVae = getState().generation.vae;
|
const currentVae = getState().generation.vae;
|
||||||
|
|
||||||
@ -136,9 +105,7 @@ export const addModelsLoadedListener = () => {
|
|||||||
|
|
||||||
const isCurrentVAEAvailable = some(
|
const isCurrentVAEAvailable = some(
|
||||||
action.payload.entities,
|
action.payload.entities,
|
||||||
(m) =>
|
(m) => m?.model_name === currentVae?.model_name && m?.base_model === currentVae?.base_model
|
||||||
m?.model_name === currentVae?.model_name &&
|
|
||||||
m?.base_model === currentVae?.base_model
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isCurrentVAEAvailable) {
|
if (isCurrentVAEAvailable) {
|
||||||
@ -156,10 +123,7 @@ export const addModelsLoadedListener = () => {
|
|||||||
const result = zParameterVAEModel.safeParse(firstModel);
|
const result = zParameterVAEModel.safeParse(firstModel);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
log.error(
|
log.error({ error: result.error.format() }, 'Failed to parse VAE model');
|
||||||
{ error: result.error.format() },
|
|
||||||
'Failed to parse VAE model'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,19 +135,14 @@ export const addModelsLoadedListener = () => {
|
|||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// LoRA models loaded - need to remove missing LoRAs from state
|
// LoRA models loaded - need to remove missing LoRAs from state
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `LoRAs loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`LoRAs loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const loras = getState().lora.loras;
|
const loras = getState().lora.loras;
|
||||||
|
|
||||||
forEach(loras, (lora, id) => {
|
forEach(loras, (lora, id) => {
|
||||||
const isLoRAAvailable = some(
|
const isLoRAAvailable = some(
|
||||||
action.payload.entities,
|
action.payload.entities,
|
||||||
(m) =>
|
(m) => m?.model_name === lora?.model_name && m?.base_model === lora?.base_model
|
||||||
m?.model_name === lora?.model_name &&
|
|
||||||
m?.base_model === lora?.base_model
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoRAAvailable) {
|
if (isLoRAAvailable) {
|
||||||
@ -199,17 +158,12 @@ export const addModelsLoadedListener = () => {
|
|||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// ControlNet models loaded - need to remove missing ControlNets from state
|
// ControlNet models loaded - need to remove missing ControlNets from state
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `ControlNet models loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`ControlNet models loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
selectAllControlNets(getState().controlAdapters).forEach((ca) => {
|
selectAllControlNets(getState().controlAdapters).forEach((ca) => {
|
||||||
const isModelAvailable = some(
|
const isModelAvailable = some(
|
||||||
action.payload.entities,
|
action.payload.entities,
|
||||||
(m) =>
|
(m) => m?.model_name === ca?.model?.model_name && m?.base_model === ca?.model?.base_model
|
||||||
m?.model_name === ca?.model?.model_name &&
|
|
||||||
m?.base_model === ca?.model?.base_model
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
@ -225,17 +179,12 @@ export const addModelsLoadedListener = () => {
|
|||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// ControlNet models loaded - need to remove missing ControlNets from state
|
// ControlNet models loaded - need to remove missing ControlNets from state
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `T2I Adapter models loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`T2I Adapter models loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
selectAllT2IAdapters(getState().controlAdapters).forEach((ca) => {
|
selectAllT2IAdapters(getState().controlAdapters).forEach((ca) => {
|
||||||
const isModelAvailable = some(
|
const isModelAvailable = some(
|
||||||
action.payload.entities,
|
action.payload.entities,
|
||||||
(m) =>
|
(m) => m?.model_name === ca?.model?.model_name && m?.base_model === ca?.model?.base_model
|
||||||
m?.model_name === ca?.model?.model_name &&
|
|
||||||
m?.base_model === ca?.model?.base_model
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
@ -251,17 +200,12 @@ export const addModelsLoadedListener = () => {
|
|||||||
effect: async (action, { getState, dispatch }) => {
|
effect: async (action, { getState, dispatch }) => {
|
||||||
// ControlNet models loaded - need to remove missing ControlNets from state
|
// ControlNet models loaded - need to remove missing ControlNets from state
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `IP Adapter models loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`IP Adapter models loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
|
|
||||||
selectAllIPAdapters(getState().controlAdapters).forEach((ca) => {
|
selectAllIPAdapters(getState().controlAdapters).forEach((ca) => {
|
||||||
const isModelAvailable = some(
|
const isModelAvailable = some(
|
||||||
action.payload.entities,
|
action.payload.entities,
|
||||||
(m) =>
|
(m) => m?.model_name === ca?.model?.model_name && m?.base_model === ca?.model?.base_model
|
||||||
m?.model_name === ca?.model?.model_name &&
|
|
||||||
m?.base_model === ca?.model?.base_model
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isModelAvailable) {
|
if (isModelAvailable) {
|
||||||
@ -276,10 +220,7 @@ export const addModelsLoadedListener = () => {
|
|||||||
matcher: modelsApi.endpoints.getTextualInversionModels.matchFulfilled,
|
matcher: modelsApi.endpoints.getTextualInversionModels.matchFulfilled,
|
||||||
effect: async (action) => {
|
effect: async (action) => {
|
||||||
const log = logger('models');
|
const log = logger('models');
|
||||||
log.info(
|
log.info({ models: action.payload.entities }, `Embeddings loaded (${action.payload.ids.length})`);
|
||||||
{ models: action.payload.entities },
|
|
||||||
`Embeddings loaded (${action.payload.ids.length})`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -15,21 +15,12 @@ import { socketConnected } from 'services/events/actions';
|
|||||||
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const matcher = isAnyOf(
|
const matcher = isAnyOf(setPositivePrompt, combinatorialToggled, maxPromptsChanged, maxPromptsReset, socketConnected);
|
||||||
setPositivePrompt,
|
|
||||||
combinatorialToggled,
|
|
||||||
maxPromptsChanged,
|
|
||||||
maxPromptsReset,
|
|
||||||
socketConnected
|
|
||||||
);
|
|
||||||
|
|
||||||
export const addDynamicPromptsListener = () => {
|
export const addDynamicPromptsListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
matcher,
|
matcher,
|
||||||
effect: async (
|
effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
|
||||||
action,
|
|
||||||
{ dispatch, getState, cancelActiveListeners, delay }
|
|
||||||
) => {
|
|
||||||
cancelActiveListeners();
|
cancelActiveListeners();
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { positivePrompt } = state.generation;
|
const { positivePrompt } = state.generation;
|
||||||
|
@ -17,16 +17,9 @@ export const addReceivedOpenAPISchemaListener = () => {
|
|||||||
log.debug({ schemaJSON }, 'Received OpenAPI schema');
|
log.debug({ schemaJSON }, 'Received OpenAPI schema');
|
||||||
const { nodesAllowlist, nodesDenylist } = getState().config;
|
const { nodesAllowlist, nodesDenylist } = getState().config;
|
||||||
|
|
||||||
const nodeTemplates = parseSchema(
|
const nodeTemplates = parseSchema(schemaJSON, nodesAllowlist, nodesDenylist);
|
||||||
schemaJSON,
|
|
||||||
nodesAllowlist,
|
|
||||||
nodesDenylist
|
|
||||||
);
|
|
||||||
|
|
||||||
log.debug(
|
log.debug({ nodeTemplates: parseify(nodeTemplates) }, `Built ${size(nodeTemplates)} node templates`);
|
||||||
{ nodeTemplates: parseify(nodeTemplates) },
|
|
||||||
`Built ${size(nodeTemplates)} node templates`
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(nodeTemplatesBuilt(nodeTemplates));
|
dispatch(nodeTemplatesBuilt(nodeTemplates));
|
||||||
},
|
},
|
||||||
@ -36,10 +29,7 @@ export const addReceivedOpenAPISchemaListener = () => {
|
|||||||
actionCreator: receivedOpenAPISchema.rejected,
|
actionCreator: receivedOpenAPISchema.rejected,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const log = logger('system');
|
const log = logger('system');
|
||||||
log.error(
|
log.error({ error: parseify(action.error) }, 'Problem retrieving OpenAPI Schema');
|
||||||
{ error: parseify(action.error) },
|
|
||||||
'Problem retrieving OpenAPI Schema'
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -16,10 +16,7 @@ const $isFirstConnection = atom(true);
|
|||||||
export const addSocketConnectedEventListener = () => {
|
export const addSocketConnectedEventListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketConnected,
|
actionCreator: socketConnected,
|
||||||
effect: async (
|
effect: async (action, { dispatch, getState, cancelActiveListeners, delay }) => {
|
||||||
action,
|
|
||||||
{ dispatch, getState, cancelActiveListeners, delay }
|
|
||||||
) => {
|
|
||||||
log.debug('Connected');
|
log.debug('Connected');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,10 +83,7 @@ export const addSocketConnectedEventListener = () => {
|
|||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const { nodeTemplates, config } = getState();
|
const { nodeTemplates, config } = getState();
|
||||||
// We only want to re-fetch the schema if we don't have any node templates
|
// We only want to re-fetch the schema if we don't have any node templates
|
||||||
if (
|
if (!size(nodeTemplates.templates) && !config.disabledTabs.includes('nodes')) {
|
||||||
!size(nodeTemplates.templates) &&
|
|
||||||
!config.disabledTabs.includes('nodes')
|
|
||||||
) {
|
|
||||||
// This request is a createAsyncThunk - resetting API state as in the above listener
|
// This request is a createAsyncThunk - resetting API state as in the above listener
|
||||||
// will not trigger this request, so we need to manually do it.
|
// will not trigger this request, so we need to manually do it.
|
||||||
dispatch(receivedOpenAPISchema());
|
dispatch(receivedOpenAPISchema());
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import { boardIdSelected, galleryViewChanged, imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
boardIdSelected,
|
|
||||||
galleryViewChanged,
|
|
||||||
imageSelected,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
|
||||||
import { isImageOutput } from 'features/nodes/types/common';
|
import { isImageOutput } from 'features/nodes/types/common';
|
||||||
import {
|
import { LINEAR_UI_OUTPUT, nodeIDDenyList } from 'features/nodes/util/graph/constants';
|
||||||
LINEAR_UI_OUTPUT,
|
|
||||||
nodeIDDenyList,
|
|
||||||
} from 'features/nodes/util/graph/constants';
|
|
||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
import { boardsApi } from 'services/api/endpoints/boards';
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
import { imagesAdapter } from 'services/api/util';
|
import { imagesAdapter } from 'services/api/util';
|
||||||
@ -29,19 +22,12 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
actionCreator: socketInvocationComplete,
|
actionCreator: socketInvocationComplete,
|
||||||
effect: async (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState }) => {
|
||||||
const { data } = action.payload;
|
const { data } = action.payload;
|
||||||
log.debug(
|
log.debug({ data: parseify(data) }, `Invocation complete (${action.payload.data.node.type})`);
|
||||||
{ data: parseify(data) },
|
|
||||||
`Invocation complete (${action.payload.data.node.type})`
|
|
||||||
);
|
|
||||||
|
|
||||||
const { result, node, queue_batch_id, source_node_id } = data;
|
const { result, node, queue_batch_id, source_node_id } = data;
|
||||||
|
|
||||||
// This complete event has an associated image output
|
// This complete event has an associated image output
|
||||||
if (
|
if (isImageOutput(result) && !nodeTypeDenylist.includes(node.type) && !nodeIDDenyList.includes(source_node_id)) {
|
||||||
isImageOutput(result) &&
|
|
||||||
!nodeTypeDenylist.includes(node.type) &&
|
|
||||||
!nodeIDDenyList.includes(source_node_id)
|
|
||||||
) {
|
|
||||||
const { image_name } = result.image;
|
const { image_name } = result.image;
|
||||||
const { canvas, gallery } = getState();
|
const { canvas, gallery } = getState();
|
||||||
|
|
||||||
@ -56,10 +42,7 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
imageDTORequest.unsubscribe();
|
imageDTORequest.unsubscribe();
|
||||||
|
|
||||||
// Add canvas images to the staging area
|
// Add canvas images to the staging area
|
||||||
if (
|
if (canvas.batchIds.includes(queue_batch_id) && [LINEAR_UI_OUTPUT].includes(data.source_node_id)) {
|
||||||
canvas.batchIds.includes(queue_batch_id) &&
|
|
||||||
[LINEAR_UI_OUTPUT].includes(data.source_node_id)
|
|
||||||
) {
|
|
||||||
dispatch(addImageToStagingArea(imageDTO));
|
dispatch(addImageToStagingArea(imageDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,21 +67,13 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
|
|
||||||
// update the total images for the board
|
// update the total images for the board
|
||||||
dispatch(
|
dispatch(
|
||||||
boardsApi.util.updateQueryData(
|
boardsApi.util.updateQueryData('getBoardImagesTotal', imageDTO.board_id ?? 'none', (draft) => {
|
||||||
'getBoardImagesTotal',
|
|
||||||
imageDTO.board_id ?? 'none',
|
|
||||||
(draft) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
draft.total += 1;
|
draft.total += 1;
|
||||||
}
|
})
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(
|
dispatch(imagesApi.util.invalidateTags([{ type: 'Board', id: imageDTO.board_id ?? 'none' }]));
|
||||||
imagesApi.util.invalidateTags([
|
|
||||||
{ type: 'Board', id: imageDTO.board_id ?? 'none' },
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
const { shouldAutoSwitch } = gallery;
|
const { shouldAutoSwitch } = gallery;
|
||||||
|
|
||||||
@ -109,10 +84,7 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
dispatch(galleryViewChanged('images'));
|
dispatch(galleryViewChanged('images'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (imageDTO.board_id && imageDTO.board_id !== gallery.selectedBoardId) {
|
||||||
imageDTO.board_id &&
|
|
||||||
imageDTO.board_id !== gallery.selectedBoardId
|
|
||||||
) {
|
|
||||||
dispatch(
|
dispatch(
|
||||||
boardIdSelected({
|
boardIdSelected({
|
||||||
boardId: imageDTO.board_id,
|
boardId: imageDTO.board_id,
|
||||||
|
@ -9,10 +9,7 @@ export const addInvocationErrorEventListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketInvocationError,
|
actionCreator: socketInvocationError,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
log.error(
|
log.error(action.payload, `Invocation error (${action.payload.data.node.type})`);
|
||||||
action.payload,
|
|
||||||
`Invocation error (${action.payload.data.node.type})`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -9,10 +9,7 @@ export const addInvocationRetrievalErrorEventListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketInvocationRetrievalError,
|
actionCreator: socketInvocationRetrievalError,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
log.error(
|
log.error(action.payload, `Invocation retrieval error (${action.payload.data.graph_execution_state_id})`);
|
||||||
action.payload,
|
|
||||||
`Invocation retrieval error (${action.payload.data.graph_execution_state_id})`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -9,10 +9,7 @@ export const addInvocationStartedEventListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketInvocationStarted,
|
actionCreator: socketInvocationStarted,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
log.debug(
|
log.debug(action.payload, `Invocation started (${action.payload.data.node.type})`);
|
||||||
action.payload,
|
|
||||||
`Invocation started (${action.payload.data.node.type})`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import {
|
import { socketModelLoadCompleted, socketModelLoadStarted } from 'services/events/actions';
|
||||||
socketModelLoadCompleted,
|
|
||||||
socketModelLoadStarted,
|
|
||||||
} from 'services/events/actions';
|
|
||||||
|
|
||||||
import { startAppListening } from '../..';
|
import { startAppListening } from '../..';
|
||||||
|
|
||||||
@ -12,8 +9,7 @@ export const addModelLoadEventListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketModelLoadStarted,
|
actionCreator: socketModelLoadStarted,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const { base_model, model_name, model_type, submodel } =
|
const { base_model, model_name, model_type, submodel } = action.payload.data;
|
||||||
action.payload.data;
|
|
||||||
|
|
||||||
let message = `Model load started: ${base_model}/${model_type}/${model_name}`;
|
let message = `Model load started: ${base_model}/${model_type}/${model_name}`;
|
||||||
|
|
||||||
@ -28,8 +24,7 @@ export const addModelLoadEventListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketModelLoadCompleted,
|
actionCreator: socketModelLoadCompleted,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
const { base_model, model_name, model_type, submodel } =
|
const { base_model, model_name, model_type, submodel } = action.payload.data;
|
||||||
action.payload.data;
|
|
||||||
|
|
||||||
let message = `Model load complete: ${base_model}/${model_type}/${model_name}`;
|
let message = `Model load complete: ${base_model}/${model_type}/${model_name}`;
|
||||||
|
|
||||||
|
@ -13,10 +13,7 @@ export const addSocketQueueItemStatusChangedEventListener = () => {
|
|||||||
// we've got new status for the queue item, batch and queue
|
// we've got new status for the queue item, batch and queue
|
||||||
const { queue_item, batch_status, queue_status } = action.payload.data;
|
const { queue_item, batch_status, queue_status } = action.payload.data;
|
||||||
|
|
||||||
log.debug(
|
log.debug(action.payload, `Queue item ${queue_item.item_id} status updated: ${queue_item.status}`);
|
||||||
action.payload,
|
|
||||||
`Queue item ${queue_item.item_id} status updated: ${queue_item.status}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update this specific queue item in the list of queue items (this is the queue item DTO, without the session)
|
// Update this specific queue item in the list of queue items (this is the queue item DTO, without the session)
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -40,35 +37,23 @@ export const addSocketQueueItemStatusChangedEventListener = () => {
|
|||||||
|
|
||||||
// Update the batch status
|
// Update the batch status
|
||||||
dispatch(
|
dispatch(
|
||||||
queueApi.util.updateQueryData(
|
queueApi.util.updateQueryData('getBatchStatus', { batch_id: batch_status.batch_id }, () => batch_status)
|
||||||
'getBatchStatus',
|
|
||||||
{ batch_id: batch_status.batch_id },
|
|
||||||
() => batch_status
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update the queue item status (this is the full queue item, including the session)
|
// Update the queue item status (this is the full queue item, including the session)
|
||||||
dispatch(
|
dispatch(
|
||||||
queueApi.util.updateQueryData(
|
queueApi.util.updateQueryData('getQueueItem', queue_item.item_id, (draft) => {
|
||||||
'getQueueItem',
|
|
||||||
queue_item.item_id,
|
|
||||||
(draft) => {
|
|
||||||
if (!draft) {
|
if (!draft) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.assign(draft, queue_item);
|
Object.assign(draft, queue_item);
|
||||||
}
|
})
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Invalidate caches for things we cannot update
|
// Invalidate caches for things we cannot update
|
||||||
// TODO: technically, we could possibly update the current session queue item, but feels safer to just request it again
|
// TODO: technically, we could possibly update the current session queue item, but feels safer to just request it again
|
||||||
dispatch(
|
dispatch(
|
||||||
queueApi.util.invalidateTags([
|
queueApi.util.invalidateTags(['CurrentSessionQueueItem', 'NextSessionQueueItem', 'InvocationCacheStatus'])
|
||||||
'CurrentSessionQueueItem',
|
|
||||||
'NextSessionQueueItem',
|
|
||||||
'InvocationCacheStatus',
|
|
||||||
])
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -9,10 +9,7 @@ export const addSessionRetrievalErrorEventListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: socketSessionRetrievalError,
|
actionCreator: socketSessionRetrievalError,
|
||||||
effect: (action) => {
|
effect: (action) => {
|
||||||
log.error(
|
log.error(action.payload, `Session retrieval error (${action.payload.data.graph_execution_state_id})`);
|
||||||
action.payload,
|
|
||||||
`Session retrieval error (${action.payload.data.graph_execution_state_id})`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -3,10 +3,7 @@ import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
|||||||
import { nodeReplaced } from 'features/nodes/store/nodesSlice';
|
import { nodeReplaced } from 'features/nodes/store/nodesSlice';
|
||||||
import { NodeUpdateError } from 'features/nodes/types/error';
|
import { NodeUpdateError } from 'features/nodes/types/error';
|
||||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||||
import {
|
import { getNeedsUpdate, updateNode } from 'features/nodes/util/node/nodeUpdate';
|
||||||
getNeedsUpdate,
|
|
||||||
updateNode,
|
|
||||||
} from 'features/nodes/util/node/nodeUpdate';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
@ -10,9 +10,7 @@ import type { BatchConfig, ImageDTO } from 'services/api/types';
|
|||||||
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(
|
export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(`upscale/upscaleRequested`);
|
||||||
`upscale/upscaleRequested`
|
|
||||||
);
|
|
||||||
|
|
||||||
export const addUpscaleRequestedListener = () => {
|
export const addUpscaleRequestedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -24,8 +22,7 @@ export const addUpscaleRequestedListener = () => {
|
|||||||
const { image_name } = imageDTO;
|
const { image_name } = imageDTO;
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const { isAllowedToUpscale, detailTKey } =
|
const { isAllowedToUpscale, detailTKey } = createIsAllowedToUpscaleSelector(imageDTO)(state);
|
||||||
createIsAllowedToUpscaleSelector(imageDTO)(state);
|
|
||||||
|
|
||||||
// if we can't upscale, show a toast and return
|
// if we can't upscale, show a toast and return
|
||||||
if (!isAllowedToUpscale) {
|
if (!isAllowedToUpscale) {
|
||||||
@ -66,21 +63,11 @@ export const addUpscaleRequestedListener = () => {
|
|||||||
|
|
||||||
const enqueueResult = await req.unwrap();
|
const enqueueResult = await req.unwrap();
|
||||||
req.reset();
|
req.reset();
|
||||||
log.debug(
|
log.debug({ enqueueResult: parseify(enqueueResult) }, t('queue.graphQueued'));
|
||||||
{ enqueueResult: parseify(enqueueResult) },
|
|
||||||
t('queue.graphQueued')
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error({ enqueueBatchArg: parseify(enqueueBatchArg) }, t('queue.graphFailedToQueue'));
|
||||||
{ enqueueBatchArg: parseify(enqueueBatchArg) },
|
|
||||||
t('queue.graphFailedToQueue')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (error instanceof Object && 'status' in error && error.status === 403) {
|
||||||
error instanceof Object &&
|
|
||||||
'status' in error &&
|
|
||||||
error.status === 403
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
dispatch(
|
dispatch(
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { parseify } from 'common/util/serialize';
|
import { parseify } from 'common/util/serialize';
|
||||||
import {
|
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
|
||||||
workflowLoaded,
|
|
||||||
workflowLoadRequested,
|
|
||||||
} from 'features/nodes/store/actions';
|
|
||||||
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
||||||
import {
|
import { WorkflowMigrationError, WorkflowVersionError } from 'features/nodes/types/error';
|
||||||
WorkflowMigrationError,
|
|
||||||
WorkflowVersionError,
|
|
||||||
} from 'features/nodes/types/error';
|
|
||||||
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
import { validateWorkflow } from 'features/nodes/util/workflow/validateWorkflow';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
@ -28,10 +22,7 @@ export const addWorkflowLoadRequestedListener = () => {
|
|||||||
const nodeTemplates = getState().nodeTemplates.templates;
|
const nodeTemplates = getState().nodeTemplates.templates;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { workflow: validatedWorkflow, warnings } = validateWorkflow(
|
const { workflow: validatedWorkflow, warnings } = validateWorkflow(workflow, nodeTemplates);
|
||||||
workflow,
|
|
||||||
nodeTemplates
|
|
||||||
);
|
|
||||||
|
|
||||||
if (asCopy) {
|
if (asCopy) {
|
||||||
// If we're loading a copy, we need to remove the ID so that the backend will create a new workflow
|
// If we're loading a copy, we need to remove the ID so that the backend will create a new workflow
|
||||||
@ -108,10 +99,7 @@ export const addWorkflowLoadRequestedListener = () => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Some other error occurred
|
// Some other error occurred
|
||||||
log.error(
|
log.error({ error: parseify(e) }, t('nodes.unknownErrorValidatingWorkflow'));
|
||||||
{ error: parseify(e) },
|
|
||||||
t('nodes.unknownErrorValidatingWorkflow')
|
|
||||||
);
|
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast(
|
addToast(
|
||||||
makeToast({
|
makeToast({
|
||||||
|
@ -8,6 +8,4 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const $store = atom<
|
export const $store = atom<Readonly<ReturnType<typeof createStore>> | undefined>();
|
||||||
Readonly<ReturnType<typeof createStore>> | undefined
|
|
||||||
>();
|
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||||
import { atom } from 'nanostores';
|
import { atom } from 'nanostores';
|
||||||
|
|
||||||
export const $workflowCategories = atom<WorkflowCategory[]>([
|
export const $workflowCategories = atom<WorkflowCategory[]>(['user', 'default']);
|
||||||
'user',
|
|
||||||
'default',
|
|
||||||
]);
|
|
||||||
|
@ -1,17 +1,10 @@
|
|||||||
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
|
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
|
||||||
import {
|
import { autoBatchEnhancer, combineReducers, configureStore } from '@reduxjs/toolkit';
|
||||||
autoBatchEnhancer,
|
|
||||||
combineReducers,
|
|
||||||
configureStore,
|
|
||||||
} from '@reduxjs/toolkit';
|
|
||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
import { idbKeyValDriver } from 'app/store/enhancers/reduxRemember/driver';
|
||||||
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
import { errorHandler } from 'app/store/enhancers/reduxRemember/errors';
|
||||||
import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
|
import { canvasPersistDenylist } from 'features/canvas/store/canvasPersistDenylist';
|
||||||
import canvasReducer, {
|
import canvasReducer, { initialCanvasState, migrateCanvasState } from 'features/canvas/store/canvasSlice';
|
||||||
initialCanvasState,
|
|
||||||
migrateCanvasState,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import changeBoardModalReducer from 'features/changeBoardModal/store/slice';
|
import changeBoardModalReducer from 'features/changeBoardModal/store/slice';
|
||||||
import { controlAdaptersPersistDenylist } from 'features/controlAdapters/store/controlAdaptersPersistDenylist';
|
import { controlAdaptersPersistDenylist } from 'features/controlAdapters/store/controlAdaptersPersistDenylist';
|
||||||
import controlAdaptersReducer, {
|
import controlAdaptersReducer, {
|
||||||
@ -25,32 +18,17 @@ import dynamicPromptsReducer, {
|
|||||||
migrateDynamicPromptsState,
|
migrateDynamicPromptsState,
|
||||||
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
} from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||||
import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
|
import { galleryPersistDenylist } from 'features/gallery/store/galleryPersistDenylist';
|
||||||
import galleryReducer, {
|
import galleryReducer, { initialGalleryState, migrateGalleryState } from 'features/gallery/store/gallerySlice';
|
||||||
initialGalleryState,
|
import hrfReducer, { initialHRFState, migrateHRFState } from 'features/hrf/store/hrfSlice';
|
||||||
migrateGalleryState,
|
import loraReducer, { initialLoraState, migrateLoRAState } from 'features/lora/store/loraSlice';
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import hrfReducer, {
|
|
||||||
initialHRFState,
|
|
||||||
migrateHRFState,
|
|
||||||
} from 'features/hrf/store/hrfSlice';
|
|
||||||
import loraReducer, {
|
|
||||||
initialLoraState,
|
|
||||||
migrateLoRAState,
|
|
||||||
} from 'features/lora/store/loraSlice';
|
|
||||||
import modelmanagerReducer, {
|
import modelmanagerReducer, {
|
||||||
initialModelManagerState,
|
initialModelManagerState,
|
||||||
migrateModelManagerState,
|
migrateModelManagerState,
|
||||||
} from 'features/modelManager/store/modelManagerSlice';
|
} from 'features/modelManager/store/modelManagerSlice';
|
||||||
import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
|
import { nodesPersistDenylist } from 'features/nodes/store/nodesPersistDenylist';
|
||||||
import nodesReducer, {
|
import nodesReducer, { initialNodesState, migrateNodesState } from 'features/nodes/store/nodesSlice';
|
||||||
initialNodesState,
|
|
||||||
migrateNodesState,
|
|
||||||
} from 'features/nodes/store/nodesSlice';
|
|
||||||
import nodeTemplatesReducer from 'features/nodes/store/nodeTemplatesSlice';
|
import nodeTemplatesReducer from 'features/nodes/store/nodeTemplatesSlice';
|
||||||
import workflowReducer, {
|
import workflowReducer, { initialWorkflowState, migrateWorkflowState } from 'features/nodes/store/workflowSlice';
|
||||||
initialWorkflowState,
|
|
||||||
migrateWorkflowState,
|
|
||||||
} from 'features/nodes/store/workflowSlice';
|
|
||||||
import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist';
|
import { generationPersistDenylist } from 'features/parameters/store/generationPersistDenylist';
|
||||||
import generationReducer, {
|
import generationReducer, {
|
||||||
initialGenerationState,
|
initialGenerationState,
|
||||||
@ -62,21 +40,12 @@ import postprocessingReducer, {
|
|||||||
migratePostprocessingState,
|
migratePostprocessingState,
|
||||||
} from 'features/parameters/store/postprocessingSlice';
|
} from 'features/parameters/store/postprocessingSlice';
|
||||||
import queueReducer from 'features/queue/store/queueSlice';
|
import queueReducer from 'features/queue/store/queueSlice';
|
||||||
import sdxlReducer, {
|
import sdxlReducer, { initialSDXLState, migrateSDXLState } from 'features/sdxl/store/sdxlSlice';
|
||||||
initialSDXLState,
|
|
||||||
migrateSDXLState,
|
|
||||||
} from 'features/sdxl/store/sdxlSlice';
|
|
||||||
import configReducer from 'features/system/store/configSlice';
|
import configReducer from 'features/system/store/configSlice';
|
||||||
import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
|
import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
|
||||||
import systemReducer, {
|
import systemReducer, { initialSystemState, migrateSystemState } from 'features/system/store/systemSlice';
|
||||||
initialSystemState,
|
|
||||||
migrateSystemState,
|
|
||||||
} from 'features/system/store/systemSlice';
|
|
||||||
import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
|
import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
|
||||||
import uiReducer, {
|
import uiReducer, { initialUIState, migrateUIState } from 'features/ui/store/uiSlice';
|
||||||
initialUIState,
|
|
||||||
migrateUIState,
|
|
||||||
} from 'features/ui/store/uiSlice';
|
|
||||||
import { diff } from 'jsondiffpatch';
|
import { diff } from 'jsondiffpatch';
|
||||||
import { defaultsDeep, keys, omit, pick } from 'lodash-es';
|
import { defaultsDeep, keys, omit, pick } from 'lodash-es';
|
||||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||||
@ -206,10 +175,7 @@ const unserialize: UnserializeFunction = (data, key) => {
|
|||||||
);
|
);
|
||||||
return transformed;
|
return transformed;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.warn(
|
log.warn({ error: serializeError(err) }, `Error rehydrating slice "${key}", falling back to default initial state`);
|
||||||
{ error: serializeError(err) },
|
|
||||||
`Error rehydrating slice "${key}", falling back to default initial state`
|
|
||||||
);
|
|
||||||
return config.initialState;
|
return config.initialState;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -229,10 +195,7 @@ const serializationDenylist: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const serialize: SerializeFunction = (data, key) => {
|
export const serialize: SerializeFunction = (data, key) => {
|
||||||
const result = omit(
|
const result = omit(data, serializationDenylist[key as keyof typeof serializationDenylist] ?? []);
|
||||||
data,
|
|
||||||
serializationDenylist[key as keyof typeof serializationDenylist] ?? []
|
|
||||||
);
|
|
||||||
return JSON.stringify(result);
|
return JSON.stringify(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -256,9 +219,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
|||||||
persistDebounce: 300,
|
persistDebounce: 300,
|
||||||
serialize,
|
serialize,
|
||||||
unserialize,
|
unserialize,
|
||||||
prefix: uniqueStoreKey
|
prefix: uniqueStoreKey ? `${STORAGE_PREFIX}${uniqueStoreKey}-` : STORAGE_PREFIX,
|
||||||
? `${STORAGE_PREFIX}${uniqueStoreKey}-`
|
|
||||||
: STORAGE_PREFIX,
|
|
||||||
errorHandler,
|
errorHandler,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -278,9 +239,7 @@ export const createStore = (uniqueStoreKey?: string, persist = true) =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppGetState = ReturnType<
|
export type AppGetState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
||||||
ReturnType<typeof createStore>['getState']
|
|
||||||
>;
|
|
||||||
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
|
||||||
|
@ -1,17 +1,9 @@
|
|||||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||||
import {
|
import { CompositeNumberInput, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
CompositeNumberInput,
|
|
||||||
Flex,
|
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { RgbaColorPicker } from 'react-colorful';
|
import { RgbaColorPicker } from 'react-colorful';
|
||||||
import type {
|
import type { ColorPickerBaseProps, RgbaColor } from 'react-colorful/dist/types';
|
||||||
ColorPickerBaseProps,
|
|
||||||
RgbaColor,
|
|
||||||
} from 'react-colorful/dist/types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
|
type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
|
||||||
@ -39,30 +31,13 @@ const numberInputWidth: ChakraProps['w'] = '4.2rem';
|
|||||||
const IAIColorPicker = (props: IAIColorPickerProps) => {
|
const IAIColorPicker = (props: IAIColorPickerProps) => {
|
||||||
const { color, onChange, withNumberInput, ...rest } = props;
|
const { color, onChange, withNumberInput, ...rest } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleChangeR = useCallback(
|
const handleChangeR = useCallback((r: number) => onChange({ ...color, r }), [color, onChange]);
|
||||||
(r: number) => onChange({ ...color, r }),
|
const handleChangeG = useCallback((g: number) => onChange({ ...color, g }), [color, onChange]);
|
||||||
[color, onChange]
|
const handleChangeB = useCallback((b: number) => onChange({ ...color, b }), [color, onChange]);
|
||||||
);
|
const handleChangeA = useCallback((a: number) => onChange({ ...color, a }), [color, onChange]);
|
||||||
const handleChangeG = useCallback(
|
|
||||||
(g: number) => onChange({ ...color, g }),
|
|
||||||
[color, onChange]
|
|
||||||
);
|
|
||||||
const handleChangeB = useCallback(
|
|
||||||
(b: number) => onChange({ ...color, b }),
|
|
||||||
[color, onChange]
|
|
||||||
);
|
|
||||||
const handleChangeA = useCallback(
|
|
||||||
(a: number) => onChange({ ...color, a }),
|
|
||||||
[color, onChange]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Flex sx={sx}>
|
<Flex sx={sx}>
|
||||||
<RgbaColorPicker
|
<RgbaColorPicker color={color} onChange={onChange} style={colorPickerStyles} {...rest} />
|
||||||
color={color}
|
|
||||||
onChange={onChange}
|
|
||||||
style={colorPickerStyles}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
{withNumberInput && (
|
{withNumberInput && (
|
||||||
<Flex>
|
<Flex>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
@ -1,26 +1,11 @@
|
|||||||
import type {
|
import type { ChakraProps, FlexProps, SystemStyleObject } from '@invoke-ai/ui-library';
|
||||||
ChakraProps,
|
|
||||||
FlexProps,
|
|
||||||
SystemStyleObject,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { Flex, Icon, Image } from '@invoke-ai/ui-library';
|
import { Flex, Icon, Image } from '@invoke-ai/ui-library';
|
||||||
import {
|
import { IAILoadingImageFallback, IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
IAILoadingImageFallback,
|
|
||||||
IAINoContentFallback,
|
|
||||||
} from 'common/components/IAIImageFallback';
|
|
||||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||||
import type {
|
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||||
TypesafeDraggableData,
|
|
||||||
TypesafeDroppableData,
|
|
||||||
} from 'features/dnd/types';
|
|
||||||
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
|
||||||
import type {
|
import type { MouseEvent, ReactElement, ReactNode, SyntheticEvent } from 'react';
|
||||||
MouseEvent,
|
|
||||||
ReactElement,
|
|
||||||
ReactNode,
|
|
||||||
SyntheticEvent,
|
|
||||||
} from 'react';
|
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { PiImageBold, PiUploadSimpleBold } from 'react-icons/pi';
|
import { PiImageBold, PiUploadSimpleBold } from 'react-icons/pi';
|
||||||
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
import type { ImageDTO, PostUploadAction } from 'services/api/types';
|
||||||
@ -165,14 +150,8 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
<Image
|
<Image
|
||||||
src={thumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
|
src={thumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
|
||||||
fallbackStrategy="beforeLoadOrError"
|
fallbackStrategy="beforeLoadOrError"
|
||||||
fallbackSrc={
|
fallbackSrc={useThumbailFallback ? imageDTO.thumbnail_url : undefined}
|
||||||
useThumbailFallback ? imageDTO.thumbnail_url : undefined
|
fallback={useThumbailFallback ? undefined : <IAILoadingImageFallback image={imageDTO} />}
|
||||||
}
|
|
||||||
fallback={
|
|
||||||
useThumbailFallback ? undefined : (
|
|
||||||
<IAILoadingImageFallback image={imageDTO} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onError={onError}
|
onError={onError}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
w={imageDTO.width}
|
w={imageDTO.width}
|
||||||
@ -183,13 +162,8 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
sx={imageSx}
|
sx={imageSx}
|
||||||
data-testid={dataTestId}
|
data-testid={dataTestId}
|
||||||
/>
|
/>
|
||||||
{withMetadataOverlay && (
|
{withMetadataOverlay && <ImageMetadataOverlay imageDTO={imageDTO} />}
|
||||||
<ImageMetadataOverlay imageDTO={imageDTO} />
|
<SelectionOverlay isSelected={isSelected} isHovered={withHoverOverlay ? isHovered : false} />
|
||||||
)}
|
|
||||||
<SelectionOverlay
|
|
||||||
isSelected={isSelected}
|
|
||||||
isHovered={withHoverOverlay ? isHovered : false}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{!imageDTO && !isUploadDisabled && (
|
{!imageDTO && !isUploadDisabled && (
|
||||||
@ -202,20 +176,10 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
)}
|
)}
|
||||||
{!imageDTO && isUploadDisabled && noContentFallback}
|
{!imageDTO && isUploadDisabled && noContentFallback}
|
||||||
{imageDTO && !isDragDisabled && (
|
{imageDTO && !isDragDisabled && (
|
||||||
<IAIDraggable
|
<IAIDraggable data={draggableData} disabled={isDragDisabled || !imageDTO} onClick={onClick} />
|
||||||
data={draggableData}
|
|
||||||
disabled={isDragDisabled || !imageDTO}
|
|
||||||
onClick={onClick}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{children}
|
{children}
|
||||||
{!isDropDisabled && (
|
{!isDropDisabled && <IAIDroppable data={droppableData} disabled={isDropDisabled} dropLabel={dropLabel} />}
|
||||||
<IAIDroppable
|
|
||||||
data={droppableData}
|
|
||||||
disabled={isDropDisabled}
|
|
||||||
dropLabel={dropLabel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</ImageContextMenu>
|
</ImageContextMenu>
|
||||||
|
@ -27,12 +27,7 @@ const IAIDropOverlay = (props: Props) => {
|
|||||||
const { isOver, label = t('gallery.drop') } = props;
|
const { isOver, label = t('gallery.drop') } = props;
|
||||||
const motionId = useRef(uuidv4());
|
const motionId = useRef(uuidv4());
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div key={motionId.current} initial={initial} animate={animate} exit={exit}>
|
||||||
key={motionId.current}
|
|
||||||
initial={initial}
|
|
||||||
animate={animate}
|
|
||||||
exit={exit}
|
|
||||||
>
|
|
||||||
<Flex position="absolute" top={0} insetInlineStart={0} w="full" h="full">
|
<Flex position="absolute" top={0} insetInlineStart={0} w="full" h="full">
|
||||||
<Flex
|
<Flex
|
||||||
position="absolute"
|
position="absolute"
|
||||||
|
@ -36,9 +36,7 @@ const IAIDroppable = (props: IAIDroppableProps) => {
|
|||||||
pointerEvents={active ? 'auto' : 'none'}
|
pointerEvents={active ? 'auto' : 'none'}
|
||||||
>
|
>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{isValidDrop(data, active) && (
|
{isValidDrop(data, active) && <IAIDropOverlay isOver={isOver} label={dropLabel} />}
|
||||||
<IAIDropOverlay isOver={isOver} label={dropLabel} />
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -16,13 +16,7 @@ const skeletonStyles: SystemStyleObject = {
|
|||||||
const IAIFillSkeleton = () => {
|
const IAIFillSkeleton = () => {
|
||||||
return (
|
return (
|
||||||
<Skeleton sx={skeletonStyles}>
|
<Skeleton sx={skeletonStyles}>
|
||||||
<Box
|
<Box position="absolute" top={0} insetInlineStart={0} height="full" width="full" />
|
||||||
position="absolute"
|
|
||||||
top={0}
|
|
||||||
insetInlineStart={0}
|
|
||||||
height="full"
|
|
||||||
width="full"
|
|
||||||
/>
|
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -19,15 +19,7 @@ export const IAILoadingImageFallback = memo((props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex opacity={0.7} w="full" h="full" alignItems="center" justifyContent="center" borderRadius="base" bg="base.900">
|
||||||
opacity={0.7}
|
|
||||||
w="full"
|
|
||||||
h="full"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
borderRadius="base"
|
|
||||||
bg="base.900"
|
|
||||||
>
|
|
||||||
<Spinner size="xl" />
|
<Spinner size="xl" />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
@ -77,8 +69,7 @@ type IAINoImageFallbackWithSpinnerProps = FlexProps & {
|
|||||||
label?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IAINoContentFallbackWithSpinner = memo(
|
export const IAINoContentFallbackWithSpinner = memo((props: IAINoImageFallbackWithSpinnerProps) => {
|
||||||
(props: IAINoImageFallbackWithSpinnerProps) => {
|
|
||||||
const { sx, ...rest } = props;
|
const { sx, ...rest } = props;
|
||||||
const styles = useMemo(
|
const styles = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -103,6 +94,5 @@ export const IAINoContentFallbackWithSpinner = memo(
|
|||||||
{props.label && <Text textAlign="center">{props.label}</Text>}
|
{props.label && <Text textAlign="center">{props.label}</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
IAINoContentFallbackWithSpinner.displayName = 'IAINoContentFallbackWithSpinner';
|
IAINoContentFallbackWithSpinner.displayName = 'IAINoContentFallbackWithSpinner';
|
||||||
|
@ -91,9 +91,7 @@ const ImageUploadOverlay = (props: ImageUploadOverlayProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Heading size="lg">{t('toast.invalidUpload')}</Heading>
|
<Heading size="lg">{t('toast.invalidUpload')}</Heading>
|
||||||
<Heading size="md">
|
<Heading size="md">{t('toast.uploadFailedInvalidUploadDesc')}</Heading>
|
||||||
{t('toast.uploadFailedInvalidUploadDesc')}
|
|
||||||
</Heading>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -28,18 +28,12 @@ type Props = {
|
|||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InformationalPopover = memo(
|
export const InformationalPopover = memo(({ feature, children, inPortal = true, ...rest }: Props) => {
|
||||||
({ feature, children, inPortal = true, ...rest }: Props) => {
|
const shouldEnableInformationalPopovers = useAppSelector((s) => s.system.shouldEnableInformationalPopovers);
|
||||||
const shouldEnableInformationalPopovers = useAppSelector(
|
|
||||||
(s) => s.system.shouldEnableInformationalPopovers
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
const data = useMemo(() => POPOVER_DATA[feature], [feature]);
|
||||||
|
|
||||||
const popoverProps = useMemo(
|
const popoverProps = useMemo(() => merge(omit(data, ['image', 'href', 'buttonLabel']), rest), [data, rest]);
|
||||||
() => merge(omit(data, ['image', 'href', 'buttonLabel']), rest),
|
|
||||||
[data, rest]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shouldEnableInformationalPopovers) {
|
if (!shouldEnableInformationalPopovers) {
|
||||||
return children;
|
return children;
|
||||||
@ -66,8 +60,7 @@ export const InformationalPopover = memo(
|
|||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
InformationalPopover.displayName = 'InformationalPopover';
|
InformationalPopover.displayName = 'InformationalPopover';
|
||||||
|
|
||||||
@ -79,10 +72,7 @@ type ContentProps = {
|
|||||||
const Content = ({ data, feature }: ContentProps) => {
|
const Content = ({ data, feature }: ContentProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const heading = useMemo<string | undefined>(
|
const heading = useMemo<string | undefined>(() => t(`popovers.${feature}.heading`), [feature, t]);
|
||||||
() => t(`popovers.${feature}.heading`),
|
|
||||||
[feature, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const paragraphs = useMemo<string[]>(
|
const paragraphs = useMemo<string[]>(
|
||||||
() =>
|
() =>
|
||||||
|
@ -95,6 +95,4 @@ export const POPOVER_DATA: { [key in Feature]?: PopoverData } = {
|
|||||||
|
|
||||||
export const OPEN_DELAY = 1000; // in milliseconds
|
export const OPEN_DELAY = 1000; // in milliseconds
|
||||||
|
|
||||||
export const POPPER_MODIFIERS: PopoverProps['modifiers'] = [
|
export const POPPER_MODIFIERS: PopoverProps['modifiers'] = [{ name: 'preventOverflow', options: { padding: 10 } }];
|
||||||
{ name: 'preventOverflow', options: { padding: 10 } },
|
|
||||||
];
|
|
||||||
|
@ -6,14 +6,7 @@ import { memo } from 'react';
|
|||||||
|
|
||||||
const Loading = () => {
|
const Loading = () => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex position="relative" width="100vw" height="100vh" alignItems="center" justifyContent="center" bg="#151519">
|
||||||
position="relative"
|
|
||||||
width="100vw"
|
|
||||||
height="100vh"
|
|
||||||
alignItems="center"
|
|
||||||
justifyContent="center"
|
|
||||||
bg="#151519"
|
|
||||||
>
|
|
||||||
<Image src={InvokeLogoWhite} w="8rem" h="8rem" />
|
<Image src={InvokeLogoWhite} w="8rem" h="8rem" />
|
||||||
<Spinner
|
<Spinner
|
||||||
label="Loading"
|
label="Loading"
|
||||||
|
@ -13,12 +13,7 @@ type Props = PropsWithChildren & {
|
|||||||
|
|
||||||
const styles: CSSProperties = { height: '100%', width: '100%' };
|
const styles: CSSProperties = { height: '100%', width: '100%' };
|
||||||
|
|
||||||
const ScrollableContent = ({
|
const ScrollableContent = ({ children, maxHeight, overflowX = 'hidden', overflowY = 'scroll' }: Props) => {
|
||||||
children,
|
|
||||||
maxHeight,
|
|
||||||
overflowX = 'hidden',
|
|
||||||
overflowY = 'scroll',
|
|
||||||
}: Props) => {
|
|
||||||
const overlayscrollbarsOptions = useMemo(
|
const overlayscrollbarsOptions = useMemo(
|
||||||
() => getOverlayScrollbarsParams(overflowX, overflowY).options,
|
() => getOverlayScrollbarsParams(overflowX, overflowY).options,
|
||||||
[overflowX, overflowY]
|
[overflowX, overflowY]
|
||||||
@ -26,11 +21,7 @@ const ScrollableContent = ({
|
|||||||
return (
|
return (
|
||||||
<Flex w="full" h="full" maxHeight={maxHeight} position="relative">
|
<Flex w="full" h="full" maxHeight={maxHeight} position="relative">
|
||||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||||
<OverlayScrollbarsComponent
|
<OverlayScrollbarsComponent defer style={styles} options={overlayscrollbarsOptions}>
|
||||||
defer
|
|
||||||
style={styles}
|
|
||||||
options={overlayscrollbarsOptions}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -14,9 +14,7 @@ const accept: Accept = {
|
|||||||
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
'image/jpeg': ['.jpg', '.jpeg', '.png'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectPostUploadAction = createMemoizedSelector(
|
const selectPostUploadAction = createMemoizedSelector(activeTabNameSelector, (activeTabName) => {
|
||||||
activeTabNameSelector,
|
|
||||||
(activeTabName) => {
|
|
||||||
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
let postUploadAction: PostUploadAction = { type: 'TOAST' };
|
||||||
|
|
||||||
if (activeTabName === 'unifiedCanvas') {
|
if (activeTabName === 'unifiedCanvas') {
|
||||||
@ -28,8 +26,7 @@ const selectPostUploadAction = createMemoizedSelector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return postUploadAction;
|
return postUploadAction;
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
export const useFullscreenDropzone = () => {
|
export const useFullscreenDropzone = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -112,9 +109,7 @@ export const useFullscreenDropzone = () => {
|
|||||||
// Set the files on the dropzone.inputRef
|
// Set the files on the dropzone.inputRef
|
||||||
dropzone.inputRef.current.files = e.clipboardData.files;
|
dropzone.inputRef.current.files = e.clipboardData.files;
|
||||||
// Dispatch the change event, dropzone catches this and we get to use its own validation
|
// Dispatch the change event, dropzone catches this and we get to use its own validation
|
||||||
dropzone.inputRef.current?.dispatchEvent(
|
dropzone.inputRef.current?.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
new Event('change', { bubbles: true })
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,13 +9,8 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
|
|
||||||
export const useGlobalHotkeys = () => {
|
export const useGlobalHotkeys = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isModelManagerEnabled =
|
const isModelManagerEnabled = useFeatureStatus('modelManager').isFeatureEnabled;
|
||||||
useFeatureStatus('modelManager').isFeatureEnabled;
|
const { queueBack, isDisabled: isDisabledQueueBack, isLoading: isLoadingQueueBack } = useQueueBack();
|
||||||
const {
|
|
||||||
queueBack,
|
|
||||||
isDisabled: isDisabledQueueBack,
|
|
||||||
isLoading: isLoadingQueueBack,
|
|
||||||
} = useQueueBack();
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['ctrl+enter', 'meta+enter'],
|
['ctrl+enter', 'meta+enter'],
|
||||||
@ -28,11 +23,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
[queueBack, isDisabledQueueBack, isLoadingQueueBack]
|
[queueBack, isDisabledQueueBack, isLoadingQueueBack]
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { queueFront, isDisabled: isDisabledQueueFront, isLoading: isLoadingQueueFront } = useQueueFront();
|
||||||
queueFront,
|
|
||||||
isDisabled: isDisabledQueueFront,
|
|
||||||
isLoading: isLoadingQueueFront,
|
|
||||||
} = useQueueFront();
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['ctrl+shift+enter', 'meta+shift+enter'],
|
['ctrl+shift+enter', 'meta+shift+enter'],
|
||||||
@ -61,11 +52,7 @@ export const useGlobalHotkeys = () => {
|
|||||||
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
|
[cancelQueueItem, isDisabledCancelQueueItem, isLoadingCancelQueueItem]
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const { clearQueue, isDisabled: isDisabledClearQueue, isLoading: isLoadingClearQueue } = useClearQueue();
|
||||||
clearQueue,
|
|
||||||
isDisabled: isDisabledClearQueue,
|
|
||||||
isLoading: isLoadingClearQueue,
|
|
||||||
} = useClearQueue();
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['ctrl+shift+x', 'meta+shift+x'],
|
['ctrl+shift+x', 'meta+shift+x'],
|
||||||
|
@ -28,11 +28,8 @@ export const useGroupedModelCombobox = <T extends AnyModelConfigEntity>(
|
|||||||
arg: UseGroupedModelComboboxArg<T>
|
arg: UseGroupedModelComboboxArg<T>
|
||||||
): UseGroupedModelComboboxReturn => {
|
): UseGroupedModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const base_model = useAppSelector(
|
const base_model = useAppSelector((s) => s.generation.model?.base_model ?? 'sdxl');
|
||||||
(s) => s.generation.model?.base_model ?? 'sdxl'
|
const { modelEntities, selectedModel, getIsDisabled, onChange, isLoading } = arg;
|
||||||
);
|
|
||||||
const { modelEntities, selectedModel, getIsDisabled, onChange, isLoading } =
|
|
||||||
arg;
|
|
||||||
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
const options = useMemo<GroupBase<ComboboxOption>[]>(() => {
|
||||||
if (!modelEntities) {
|
if (!modelEntities) {
|
||||||
return [];
|
return [];
|
||||||
@ -60,11 +57,8 @@ export const useGroupedModelCombobox = <T extends AnyModelConfigEntity>(
|
|||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() =>
|
() =>
|
||||||
options
|
options.flatMap((o) => o.options).find((m) => (selectedModel ? m.value === getModelId(selectedModel) : false)) ??
|
||||||
.flatMap((o) => o.options)
|
null,
|
||||||
.find((m) =>
|
|
||||||
selectedModel ? m.value === getModelId(selectedModel) : false
|
|
||||||
) ?? null,
|
|
||||||
[options, selectedModel]
|
[options, selectedModel]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -28,10 +28,7 @@ type UseImageUploadButtonArgs = {
|
|||||||
* <Button {...getUploadButtonProps()} /> // will open the file dialog on click
|
* <Button {...getUploadButtonProps()} /> // will open the file dialog on click
|
||||||
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
* <input {...getUploadInputProps()} /> // hidden, handles native upload functionality
|
||||||
*/
|
*/
|
||||||
export const useImageUploadButton = ({
|
export const useImageUploadButton = ({ postUploadAction, isDisabled }: UseImageUploadButtonArgs) => {
|
||||||
postUploadAction,
|
|
||||||
isDisabled,
|
|
||||||
}: UseImageUploadButtonArgs) => {
|
|
||||||
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
const autoAddBoardId = useAppSelector((s) => s.gallery.autoAddBoardId);
|
||||||
const [uploadImage] = useUploadImageMutation();
|
const [uploadImage] = useUploadImageMutation();
|
||||||
const onDropAccepted = useCallback(
|
const onDropAccepted = useCallback(
|
||||||
|
@ -27,15 +27,7 @@ const selector = createMemoizedSelector(
|
|||||||
selectDynamicPromptsSlice,
|
selectDynamicPromptsSlice,
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
],
|
],
|
||||||
(
|
(controlAdapters, generation, system, nodes, nodeTemplates, dynamicPrompts, activeTabName) => {
|
||||||
controlAdapters,
|
|
||||||
generation,
|
|
||||||
system,
|
|
||||||
nodes,
|
|
||||||
nodeTemplates,
|
|
||||||
dynamicPrompts,
|
|
||||||
activeTabName
|
|
||||||
) => {
|
|
||||||
const { initialImage, model, positivePrompt } = generation;
|
const { initialImage, model, positivePrompt } = generation;
|
||||||
|
|
||||||
const { isConnected } = system;
|
const { isConnected } = system;
|
||||||
@ -75,8 +67,7 @@ const selector = createMemoizedSelector(
|
|||||||
forEach(node.data.inputs, (field) => {
|
forEach(node.data.inputs, (field) => {
|
||||||
const fieldTemplate = nodeTemplate.inputs[field.name];
|
const fieldTemplate = nodeTemplate.inputs[field.name];
|
||||||
const hasConnection = connectedEdges.some(
|
const hasConnection = connectedEdges.some(
|
||||||
(edge) =>
|
(edge) => edge.target === node.id && edge.targetHandle === field.name
|
||||||
edge.target === node.id && edge.targetHandle === field.name
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!fieldTemplate) {
|
if (!fieldTemplate) {
|
||||||
@ -84,11 +75,7 @@ const selector = createMemoizedSelector(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (fieldTemplate.required && field.value === undefined && !hasConnection) {
|
||||||
fieldTemplate.required &&
|
|
||||||
field.value === undefined &&
|
|
||||||
!hasConnection
|
|
||||||
) {
|
|
||||||
reasons.push(
|
reasons.push(
|
||||||
i18n.t('parameters.invoke.missingInputForField', {
|
i18n.t('parameters.invoke.missingInputForField', {
|
||||||
nodeLabel: node.data.label || nodeTemplate.title,
|
nodeLabel: node.data.label || nodeTemplate.title,
|
||||||
@ -101,10 +88,7 @@ const selector = createMemoizedSelector(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
||||||
dynamicPrompts.prompts.length === 0 &&
|
|
||||||
getShouldProcessPrompt(positivePrompt)
|
|
||||||
) {
|
|
||||||
reasons.push(i18n.t('parameters.invoke.noPrompts'));
|
reasons.push(i18n.t('parameters.invoke.noPrompts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,9 +118,7 @@ const selector = createMemoizedSelector(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!ca.controlImage ||
|
!ca.controlImage ||
|
||||||
(isControlNetOrT2IAdapter(ca) &&
|
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
||||||
!ca.processedControlImage &&
|
|
||||||
ca.processorType !== 'none')
|
|
||||||
) {
|
) {
|
||||||
reasons.push(
|
reasons.push(
|
||||||
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
||||||
|
@ -27,14 +27,7 @@ export const useModelCombobox = <T extends AnyModelConfigEntity>(
|
|||||||
arg: UseModelComboboxArg<T>
|
arg: UseModelComboboxArg<T>
|
||||||
): UseModelComboboxReturn => {
|
): UseModelComboboxReturn => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const { modelEntities, selectedModel, getIsDisabled, onChange, isLoading, optionsFilter = () => true } = arg;
|
||||||
modelEntities,
|
|
||||||
selectedModel,
|
|
||||||
getIsDisabled,
|
|
||||||
onChange,
|
|
||||||
isLoading,
|
|
||||||
optionsFilter = () => true,
|
|
||||||
} = arg;
|
|
||||||
const options = useMemo<ComboboxOption[]>(() => {
|
const options = useMemo<ComboboxOption[]>(() => {
|
||||||
if (!modelEntities) {
|
if (!modelEntities) {
|
||||||
return [];
|
return [];
|
||||||
@ -49,10 +42,7 @@ export const useModelCombobox = <T extends AnyModelConfigEntity>(
|
|||||||
}, [optionsFilter, getIsDisabled, modelEntities]);
|
}, [optionsFilter, getIsDisabled, modelEntities]);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() =>
|
() => options.find((m) => (selectedModel ? m.value === getModelId(selectedModel) : false)),
|
||||||
options.find((m) =>
|
|
||||||
selectedModel ? m.value === getModelId(selectedModel) : false
|
|
||||||
),
|
|
||||||
[options, selectedModel]
|
[options, selectedModel]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -20,12 +20,7 @@ export const areAnyPixelsBlack = (pixels: Uint8ClampedArray) => {
|
|||||||
const len = pixels.length;
|
const len = pixels.length;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (i; i < len; ) {
|
for (i; i < len; ) {
|
||||||
if (
|
if (pixels[i++] === 0 && pixels[i++] === 0 && pixels[i++] === 0 && pixels[i++] === 255) {
|
||||||
pixels[i++] === 0 &&
|
|
||||||
pixels[i++] === 0 &&
|
|
||||||
pixels[i++] === 0 &&
|
|
||||||
pixels[i++] === 255
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export const colorTokenToCssVar = (colorToken: string) =>
|
export const colorTokenToCssVar = (colorToken: string) => `var(--invoke-colors-${colorToken.split('.').join('-')})`;
|
||||||
`var(--invoke-colors-${colorToken.split('.').join('-')})`;
|
|
||||||
|
@ -8,12 +8,7 @@ export type GenerateSeedsArg = {
|
|||||||
max?: number;
|
max?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateSeeds = ({
|
export const generateSeeds = ({ count, start, min = NUMPY_RAND_MIN, max = NUMPY_RAND_MAX }: GenerateSeedsArg) => {
|
||||||
count,
|
|
||||||
start,
|
|
||||||
min = NUMPY_RAND_MIN,
|
|
||||||
max = NUMPY_RAND_MAX,
|
|
||||||
}: GenerateSeedsArg) => {
|
|
||||||
const first = start ?? random(min, max);
|
const first = start ?? random(min, max);
|
||||||
const seeds: number[] = [];
|
const seeds: number[] = [];
|
||||||
for (let i = first; i < first + count; i++) {
|
for (let i = first; i < first + count; i++) {
|
||||||
@ -22,7 +17,4 @@ export const generateSeeds = ({
|
|||||||
return seeds;
|
return seeds;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateOneSeed = (
|
export const generateOneSeed = (min: number = NUMPY_RAND_MIN, max: number = NUMPY_RAND_MAX) => random(min, max);
|
||||||
min: number = NUMPY_RAND_MIN,
|
|
||||||
max: number = NUMPY_RAND_MAX
|
|
||||||
) => random(min, max);
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
export const roundDownToMultiple = (num: number, multiple: number): number => {
|
export const roundDownToMultiple = (num: number, multiple: number): number => {
|
||||||
return Math.floor(num / multiple) * multiple;
|
return Math.floor(num / multiple) * multiple;
|
||||||
};
|
};
|
||||||
export const roundDownToMultipleMin = (
|
export const roundDownToMultipleMin = (num: number, multiple: number): number => {
|
||||||
num: number,
|
|
||||||
multiple: number
|
|
||||||
): number => {
|
|
||||||
return Math.max(multiple, Math.floor(num / multiple) * multiple);
|
return Math.max(multiple, Math.floor(num / multiple) * multiple);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Button, ConfirmationAlertDialog, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
Button,
|
|
||||||
ConfirmationAlertDialog,
|
|
||||||
useDisclosure,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
||||||
@ -15,19 +11,11 @@ const ClearCanvasHistoryButtonModal = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const acceptCallback = useCallback(
|
const acceptCallback = useCallback(() => dispatch(clearCanvasHistory()), [dispatch]);
|
||||||
() => dispatch(clearCanvasHistory()),
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button onClick={onOpen} size="sm" leftIcon={<PiTrashSimpleFill />} isDisabled={isStaging}>
|
||||||
onClick={onOpen}
|
|
||||||
size="sm"
|
|
||||||
leftIcon={<PiTrashSimpleFill />}
|
|
||||||
isDisabled={isStaging}
|
|
||||||
>
|
|
||||||
{t('unifiedCanvas.clearCanvasHistory')}
|
{t('unifiedCanvas.clearCanvasHistory')}
|
||||||
</Button>
|
</Button>
|
||||||
<ConfirmationAlertDialog
|
<ConfirmationAlertDialog
|
||||||
|
@ -19,10 +19,7 @@ import {
|
|||||||
$tool,
|
$tool,
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
} from 'features/canvas/store/canvasNanostore';
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import {
|
import { canvasResized, selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||||
canvasResized,
|
|
||||||
selectCanvasSlice,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import type { Vector2d } from 'konva/lib/types';
|
import type { Vector2d } from 'konva/lib/types';
|
||||||
@ -55,18 +52,12 @@ const ChakraStage = chakra(Stage, {
|
|||||||
const IAICanvas = () => {
|
const IAICanvas = () => {
|
||||||
const isStaging = useAppSelector(isStagingSelector);
|
const isStaging = useAppSelector(isStagingSelector);
|
||||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||||
const shouldShowBoundingBox = useAppSelector(
|
const shouldShowBoundingBox = useAppSelector((s) => s.canvas.shouldShowBoundingBox);
|
||||||
(s) => s.canvas.shouldShowBoundingBox
|
|
||||||
);
|
|
||||||
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
|
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
|
||||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||||
const shouldShowIntermediates = useAppSelector(
|
const shouldShowIntermediates = useAppSelector((s) => s.canvas.shouldShowIntermediates);
|
||||||
(s) => s.canvas.shouldShowIntermediates
|
|
||||||
);
|
|
||||||
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
|
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
|
||||||
const shouldRestrictStrokesToBox = useAppSelector(
|
const shouldRestrictStrokesToBox = useAppSelector((s) => s.canvas.shouldRestrictStrokesToBox);
|
||||||
(s) => s.canvas.shouldRestrictStrokesToBox
|
|
||||||
);
|
|
||||||
const { stageCoordinates, stageDimensions } = useAppSelector(selector);
|
const { stageCoordinates, stageDimensions } = useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@ -95,22 +86,12 @@ const IAICanvas = () => {
|
|||||||
return 'default';
|
return 'default';
|
||||||
}
|
}
|
||||||
return 'none';
|
return 'none';
|
||||||
}, [
|
}, [isMouseOverBoundingBox, isMovingStage, isStaging, isTransformingBoundingBox, shouldRestrictStrokesToBox, tool]);
|
||||||
isMouseOverBoundingBox,
|
|
||||||
isMovingStage,
|
|
||||||
isStaging,
|
|
||||||
isTransformingBoundingBox,
|
|
||||||
shouldRestrictStrokesToBox,
|
|
||||||
tool,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const canvasBaseLayerRefCallback = useCallback(
|
const canvasBaseLayerRefCallback = useCallback((layerElement: Konva.Layer) => {
|
||||||
(layerElement: Konva.Layer) => {
|
|
||||||
$canvasBaseLayer.set(layerElement);
|
$canvasBaseLayer.set(layerElement);
|
||||||
canvasBaseLayerRef.current = layerElement;
|
canvasBaseLayerRef.current = layerElement;
|
||||||
},
|
}, []);
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastCursorPositionRef = useRef<Vector2d>({ x: 0, y: 0 });
|
const lastCursorPositionRef = useRef<Vector2d>({ x: 0, y: 0 });
|
||||||
|
|
||||||
@ -120,18 +101,10 @@ const IAICanvas = () => {
|
|||||||
const handleWheel = useCanvasWheel(stageRef);
|
const handleWheel = useCanvasWheel(stageRef);
|
||||||
const handleMouseDown = useCanvasMouseDown(stageRef);
|
const handleMouseDown = useCanvasMouseDown(stageRef);
|
||||||
const handleMouseUp = useCanvasMouseUp(stageRef, didMouseMoveRef);
|
const handleMouseUp = useCanvasMouseUp(stageRef, didMouseMoveRef);
|
||||||
const handleMouseMove = useCanvasMouseMove(
|
const handleMouseMove = useCanvasMouseMove(stageRef, didMouseMoveRef, lastCursorPositionRef);
|
||||||
stageRef,
|
const { handleDragStart, handleDragMove, handleDragEnd } = useCanvasDragMove();
|
||||||
didMouseMoveRef,
|
|
||||||
lastCursorPositionRef
|
|
||||||
);
|
|
||||||
const { handleDragStart, handleDragMove, handleDragEnd } =
|
|
||||||
useCanvasDragMove();
|
|
||||||
const handleMouseOut = useCanvasMouseOut();
|
const handleMouseOut = useCanvasMouseOut();
|
||||||
const handleContextMenu = useCallback(
|
const handleContextMenu = useCallback((e: KonvaEventObject<MouseEvent>) => e.evt.preventDefault(), []);
|
||||||
(e: KonvaEventObject<MouseEvent>) => e.evt.preventDefault(),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!containerRef.current) {
|
if (!containerRef.current) {
|
||||||
@ -169,14 +142,7 @@ const IAICanvas = () => {
|
|||||||
const scale = useMemo(() => ({ x: stageScale, y: stageScale }), [stageScale]);
|
const scale = useMemo(() => ({ x: stageScale, y: stageScale }), [stageScale]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex id="canvas-container" ref={containerRef} position="relative" height="100%" width="100%" borderRadius="base">
|
||||||
id="canvas-container"
|
|
||||||
ref={containerRef}
|
|
||||||
position="relative"
|
|
||||||
height="100%"
|
|
||||||
width="100%"
|
|
||||||
borderRadius="base"
|
|
||||||
>
|
|
||||||
<Box position="absolute">
|
<Box position="absolute">
|
||||||
<ChakraStage
|
<ChakraStage
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
@ -205,19 +171,10 @@ const IAICanvas = () => {
|
|||||||
<IAICanvasGrid />
|
<IAICanvasGrid />
|
||||||
</Layer>
|
</Layer>
|
||||||
|
|
||||||
<Layer
|
<Layer id="base" ref={canvasBaseLayerRefCallback} listening={false} imageSmoothingEnabled={shouldAntialias}>
|
||||||
id="base"
|
|
||||||
ref={canvasBaseLayerRefCallback}
|
|
||||||
listening={false}
|
|
||||||
imageSmoothingEnabled={shouldAntialias}
|
|
||||||
>
|
|
||||||
<IAICanvasObjectRenderer />
|
<IAICanvasObjectRenderer />
|
||||||
</Layer>
|
</Layer>
|
||||||
<Layer
|
<Layer id="mask" visible={isMaskEnabled && !isStaging} listening={false}>
|
||||||
id="mask"
|
|
||||||
visible={isMaskEnabled && !isStaging}
|
|
||||||
listening={false}
|
|
||||||
>
|
|
||||||
<IAICanvasMaskLines visible={true} listening={false} />
|
<IAICanvasMaskLines visible={true} listening={false} />
|
||||||
<IAICanvasMaskCompositer listening={false} />
|
<IAICanvasMaskCompositer listening={false} />
|
||||||
</Layer>
|
</Layer>
|
||||||
@ -225,17 +182,10 @@ const IAICanvas = () => {
|
|||||||
<IAICanvasBoundingBoxOverlay />
|
<IAICanvasBoundingBoxOverlay />
|
||||||
</Layer>
|
</Layer>
|
||||||
<Layer id="preview" imageSmoothingEnabled={shouldAntialias}>
|
<Layer id="preview" imageSmoothingEnabled={shouldAntialias}>
|
||||||
{!isStaging && (
|
{!isStaging && <IAICanvasToolPreview visible={tool !== 'move'} listening={false} />}
|
||||||
<IAICanvasToolPreview
|
|
||||||
visible={tool !== 'move'}
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<IAICanvasStagingArea listening={false} visible={isStaging} />
|
<IAICanvasStagingArea listening={false} visible={isStaging} />
|
||||||
{shouldShowIntermediates && <IAICanvasIntermediateImage />}
|
{shouldShowIntermediates && <IAICanvasIntermediateImage />}
|
||||||
<IAICanvasBoundingBox
|
<IAICanvasBoundingBox visible={shouldShowBoundingBox && !isStaging} />
|
||||||
visible={shouldShowBoundingBox && !isStaging}
|
|
||||||
/>
|
|
||||||
</Layer>
|
</Layer>
|
||||||
</ChakraStage>
|
</ChakraStage>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -5,12 +5,7 @@ import { memo } from 'react';
|
|||||||
import { Group, Rect } from 'react-konva';
|
import { Group, Rect } from 'react-konva';
|
||||||
|
|
||||||
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||||
const {
|
const { boundingBoxCoordinates, boundingBoxDimensions, stageDimensions, stageCoordinates } = canvas;
|
||||||
boundingBoxCoordinates,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
stageDimensions,
|
|
||||||
stageCoordinates,
|
|
||||||
} = canvas;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
boundingBoxCoordinates,
|
boundingBoxCoordinates,
|
||||||
@ -21,15 +16,8 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const IAICanvasBoundingBoxOverlay = () => {
|
const IAICanvasBoundingBoxOverlay = () => {
|
||||||
const {
|
const { boundingBoxCoordinates, boundingBoxDimensions, stageCoordinates, stageDimensions } = useAppSelector(selector);
|
||||||
boundingBoxCoordinates,
|
const shouldDarkenOutsideBoundingBox = useAppSelector((s) => s.canvas.shouldDarkenOutsideBoundingBox);
|
||||||
boundingBoxDimensions,
|
|
||||||
stageCoordinates,
|
|
||||||
stageDimensions,
|
|
||||||
} = useAppSelector(selector);
|
|
||||||
const shouldDarkenOutsideBoundingBox = useAppSelector(
|
|
||||||
(s) => s.canvas.shouldDarkenOutsideBoundingBox
|
|
||||||
);
|
|
||||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -13,13 +13,8 @@ type IAICanvasImageProps = {
|
|||||||
};
|
};
|
||||||
const IAICanvasImage = (props: IAICanvasImageProps) => {
|
const IAICanvasImage = (props: IAICanvasImageProps) => {
|
||||||
const { x, y, imageName } = props.canvasImage;
|
const { x, y, imageName } = props.canvasImage;
|
||||||
const { currentData: imageDTO, isError } = useGetImageDTOQuery(
|
const { currentData: imageDTO, isError } = useGetImageDTOQuery(imageName ?? skipToken);
|
||||||
imageName ?? skipToken
|
const [image, status] = useImage(imageDTO?.image_url ?? '', $authToken.get() ? 'use-credentials' : 'anonymous');
|
||||||
);
|
|
||||||
const [image, status] = useImage(
|
|
||||||
imageDTO?.image_url ?? '',
|
|
||||||
$authToken.get() ? 'use-credentials' : 'anonymous'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isError || status === 'failed') {
|
if (isError || status === 'failed') {
|
||||||
return <IAICanvasImageErrorFallback canvasImage={props.canvasImage} />;
|
return <IAICanvasImageErrorFallback canvasImage={props.canvasImage} />;
|
||||||
|
@ -7,9 +7,7 @@ import { Group, Rect, Text } from 'react-konva';
|
|||||||
type IAICanvasImageErrorFallbackProps = {
|
type IAICanvasImageErrorFallbackProps = {
|
||||||
canvasImage: CanvasImage;
|
canvasImage: CanvasImage;
|
||||||
};
|
};
|
||||||
const IAICanvasImageErrorFallback = ({
|
const IAICanvasImageErrorFallback = ({ canvasImage }: IAICanvasImageErrorFallbackProps) => {
|
||||||
canvasImage,
|
|
||||||
}: IAICanvasImageErrorFallbackProps) => {
|
|
||||||
const [rectFill, textFill] = useToken('colors', ['base.500', 'base.900']);
|
const [rectFill, textFill] = useToken('colors', ['base.500', 'base.900']);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
|
@ -5,26 +5,20 @@ import { selectSystemSlice } from 'features/system/store/systemSlice';
|
|||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useState } from 'react';
|
||||||
import { Image as KonvaImage } from 'react-konva';
|
import { Image as KonvaImage } from 'react-konva';
|
||||||
|
|
||||||
const progressImageSelector = createMemoizedSelector(
|
const progressImageSelector = createMemoizedSelector([selectSystemSlice, selectCanvasSlice], (system, canvas) => {
|
||||||
[selectSystemSlice, selectCanvasSlice],
|
|
||||||
(system, canvas) => {
|
|
||||||
const { denoiseProgress } = system;
|
const { denoiseProgress } = system;
|
||||||
const { batchIds } = canvas;
|
const { batchIds } = canvas;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
progressImage:
|
progressImage:
|
||||||
denoiseProgress && batchIds.includes(denoiseProgress.batch_id)
|
denoiseProgress && batchIds.includes(denoiseProgress.batch_id) ? denoiseProgress.progress_image : undefined,
|
||||||
? denoiseProgress.progress_image
|
|
||||||
: undefined,
|
|
||||||
boundingBox: canvas.layerState.stagingArea.boundingBox,
|
boundingBox: canvas.layerState.stagingArea.boundingBox,
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const IAICanvasIntermediateImage = () => {
|
const IAICanvasIntermediateImage = () => {
|
||||||
const { progressImage, boundingBox } = useAppSelector(progressImageSelector);
|
const { progressImage, boundingBox } = useAppSelector(progressImageSelector);
|
||||||
const [loadedImageElement, setLoadedImageElement] =
|
const [loadedImageElement, setLoadedImageElement] = useState<HTMLImageElement | null>(null);
|
||||||
useState<HTMLImageElement | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!progressImage) {
|
if (!progressImage) {
|
||||||
|
@ -9,30 +9,22 @@ import { isNumber } from 'lodash-es';
|
|||||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Rect } from 'react-konva';
|
import { Rect } from 'react-konva';
|
||||||
|
|
||||||
export const canvasMaskCompositerSelector = createMemoizedSelector(
|
export const canvasMaskCompositerSelector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||||
selectCanvasSlice,
|
|
||||||
(canvas) => {
|
|
||||||
return {
|
return {
|
||||||
stageCoordinates: canvas.stageCoordinates,
|
stageCoordinates: canvas.stageCoordinates,
|
||||||
stageDimensions: canvas.stageDimensions,
|
stageDimensions: canvas.stageDimensions,
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
type IAICanvasMaskCompositerProps = RectConfig;
|
type IAICanvasMaskCompositerProps = RectConfig;
|
||||||
|
|
||||||
const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
||||||
const { ...rest } = props;
|
const { ...rest } = props;
|
||||||
|
|
||||||
const { stageCoordinates, stageDimensions } = useAppSelector(
|
const { stageCoordinates, stageDimensions } = useAppSelector(canvasMaskCompositerSelector);
|
||||||
canvasMaskCompositerSelector
|
|
||||||
);
|
|
||||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||||
const maskColorString = useAppSelector((s) =>
|
const maskColorString = useAppSelector((s) => rgbaColorToString(s.canvas.maskColor));
|
||||||
rgbaColorToString(s.canvas.maskColor)
|
const [fillPatternImage, setFillPatternImage] = useState<HTMLImageElement | null>(null);
|
||||||
);
|
|
||||||
const [fillPatternImage, setFillPatternImage] =
|
|
||||||
useState<HTMLImageElement | null>(null);
|
|
||||||
|
|
||||||
const [offset, setOffset] = useState<number>(0);
|
const [offset, setOffset] = useState<number>(0);
|
||||||
|
|
||||||
@ -66,10 +58,7 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
|
|||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fillPatternScale = useMemo(
|
const fillPatternScale = useMemo(() => ({ x: 1 / stageScale, y: 1 / stageScale }), [stageScale]);
|
||||||
() => ({ x: 1 / stageScale, y: 1 / stageScale }),
|
|
||||||
[stageScale]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!fillPatternImage ||
|
!fillPatternImage ||
|
||||||
|
@ -27,9 +27,7 @@ const IAICanvasLines = (props: InpaintingCanvasLinesProps) => {
|
|||||||
lineJoin="round"
|
lineJoin="round"
|
||||||
shadowForStrokeEnabled={false}
|
shadowForStrokeEnabled={false}
|
||||||
listening={false}
|
listening={false}
|
||||||
globalCompositeOperation={
|
globalCompositeOperation={line.tool === 'brush' ? 'source-over' : 'destination-out'}
|
||||||
line.tool === 'brush' ? 'source-over' : 'destination-out'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
|
@ -31,9 +31,7 @@ const IAICanvasObjectRenderer = () => {
|
|||||||
lineJoin="round"
|
lineJoin="round"
|
||||||
shadowForStrokeEnabled={false}
|
shadowForStrokeEnabled={false}
|
||||||
listening={false}
|
listening={false}
|
||||||
globalCompositeOperation={
|
globalCompositeOperation={obj.tool === 'brush' ? 'source-over' : 'destination-out'}
|
||||||
obj.tool === 'brush' ? 'source-over' : 'destination-out'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
if (obj.clip) {
|
if (obj.clip) {
|
||||||
|
@ -22,9 +22,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
currentStagingAreaImage:
|
currentStagingAreaImage:
|
||||||
images.length > 0 && selectedImageIndex !== undefined
|
images.length > 0 && selectedImageIndex !== undefined ? images[selectedImageIndex] : undefined,
|
||||||
? images[selectedImageIndex]
|
|
||||||
: undefined,
|
|
||||||
isOnFirstImage: selectedImageIndex === 0,
|
isOnFirstImage: selectedImageIndex === 0,
|
||||||
isOnLastImage: selectedImageIndex === images.length - 1,
|
isOnLastImage: selectedImageIndex === images.length - 1,
|
||||||
shouldShowStagingImage,
|
shouldShowStagingImage,
|
||||||
@ -39,21 +37,12 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
type Props = GroupConfig;
|
type Props = GroupConfig;
|
||||||
|
|
||||||
const IAICanvasStagingArea = (props: Props) => {
|
const IAICanvasStagingArea = (props: Props) => {
|
||||||
const {
|
const { currentStagingAreaImage, shouldShowStagingImage, shouldShowStagingOutline, x, y, width, height } =
|
||||||
currentStagingAreaImage,
|
useAppSelector(selector);
|
||||||
shouldShowStagingImage,
|
|
||||||
shouldShowStagingOutline,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
} = useAppSelector(selector);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Group {...props}>
|
<Group {...props}>
|
||||||
{shouldShowStagingImage && currentStagingAreaImage && (
|
{shouldShowStagingImage && currentStagingAreaImage && <IAICanvasImage canvasImage={currentStagingAreaImage} />}
|
||||||
<IAICanvasImage canvasImage={currentStagingAreaImage} />
|
|
||||||
)}
|
|
||||||
{shouldShowStagingOutline && (
|
{shouldShowStagingOutline && (
|
||||||
<Group listening={false}>
|
<Group listening={false}>
|
||||||
<Rect
|
<Rect
|
||||||
|
@ -38,8 +38,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
return {
|
return {
|
||||||
currentIndex: selectedImageIndex,
|
currentIndex: selectedImageIndex,
|
||||||
total: images.length,
|
total: images.length,
|
||||||
currentStagingAreaImage:
|
currentStagingAreaImage: images.length > 0 ? images[selectedImageIndex] : undefined,
|
||||||
images.length > 0 ? images[selectedImageIndex] : undefined,
|
|
||||||
shouldShowStagingImage,
|
shouldShowStagingImage,
|
||||||
shouldShowStagingOutline,
|
shouldShowStagingOutline,
|
||||||
};
|
};
|
||||||
@ -47,12 +46,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
|
|
||||||
const IAICanvasStagingAreaToolbar = () => {
|
const IAICanvasStagingAreaToolbar = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const { currentStagingAreaImage, shouldShowStagingImage, currentIndex, total } = useAppSelector(selector);
|
||||||
currentStagingAreaImage,
|
|
||||||
shouldShowStagingImage,
|
|
||||||
currentIndex,
|
|
||||||
total,
|
|
||||||
} = useAppSelector(selector);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -64,20 +58,11 @@ const IAICanvasStagingAreaToolbar = () => {
|
|||||||
dispatch(setShouldShowStagingOutline(false));
|
dispatch(setShouldShowStagingOutline(false));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handlePrevImage = useCallback(
|
const handlePrevImage = useCallback(() => dispatch(prevStagingAreaImage()), [dispatch]);
|
||||||
() => dispatch(prevStagingAreaImage()),
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleNextImage = useCallback(
|
const handleNextImage = useCallback(() => dispatch(nextStagingAreaImage()), [dispatch]);
|
||||||
() => dispatch(nextStagingAreaImage()),
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAccept = useCallback(
|
const handleAccept = useCallback(() => dispatch(commitStagingAreaImage()), [dispatch]);
|
||||||
() => dispatch(commitStagingAreaImage()),
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(['left'], handlePrevImage, {
|
useHotkeys(['left'], handlePrevImage, {
|
||||||
enabled: () => true,
|
enabled: () => true,
|
||||||
@ -94,9 +79,7 @@ const IAICanvasStagingAreaToolbar = () => {
|
|||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: imageDTO } = useGetImageDTOQuery(
|
const { data: imageDTO } = useGetImageDTOQuery(currentStagingAreaImage?.imageName ?? skipToken);
|
||||||
currentStagingAreaImage?.imageName ?? skipToken
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggleShouldShowStagingImage = useCallback(() => {
|
const handleToggleShouldShowStagingImage = useCallback(() => {
|
||||||
dispatch(setShouldShowStagingImage(!shouldShowStagingImage));
|
dispatch(setShouldShowStagingImage(!shouldShowStagingImage));
|
||||||
@ -166,16 +149,8 @@ const IAICanvasStagingAreaToolbar = () => {
|
|||||||
colorScheme="invokeBlue"
|
colorScheme="invokeBlue"
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={
|
tooltip={shouldShowStagingImage ? t('unifiedCanvas.showResultsOn') : t('unifiedCanvas.showResultsOff')}
|
||||||
shouldShowStagingImage
|
aria-label={shouldShowStagingImage ? t('unifiedCanvas.showResultsOn') : t('unifiedCanvas.showResultsOff')}
|
||||||
? t('unifiedCanvas.showResultsOn')
|
|
||||||
: t('unifiedCanvas.showResultsOff')
|
|
||||||
}
|
|
||||||
aria-label={
|
|
||||||
shouldShowStagingImage
|
|
||||||
? t('unifiedCanvas.showResultsOn')
|
|
||||||
: t('unifiedCanvas.showResultsOff')
|
|
||||||
}
|
|
||||||
data-alert={!shouldShowStagingImage}
|
data-alert={!shouldShowStagingImage}
|
||||||
icon={shouldShowStagingImage ? <PiEyeBold /> : <PiEyeSlashBold />}
|
icon={shouldShowStagingImage ? <PiEyeBold /> : <PiEyeSlashBold />}
|
||||||
onClick={handleToggleShouldShowStagingImage}
|
onClick={handleToggleShouldShowStagingImage}
|
||||||
|
@ -16,10 +16,7 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
stageDimensions: { width: stageWidth, height: stageHeight },
|
stageDimensions: { width: stageWidth, height: stageHeight },
|
||||||
stageCoordinates: { x: stageX, y: stageY },
|
stageCoordinates: { x: stageX, y: stageY },
|
||||||
boundingBoxDimensions: { width: boxWidth, height: boxHeight },
|
boundingBoxDimensions: { width: boxWidth, height: boxHeight },
|
||||||
scaledBoundingBoxDimensions: {
|
scaledBoundingBoxDimensions: { width: scaledBoxWidth, height: scaledBoxHeight },
|
||||||
width: scaledBoxWidth,
|
|
||||||
height: scaledBoxHeight,
|
|
||||||
},
|
|
||||||
boundingBoxCoordinates: { x: boxX, y: boxY },
|
boundingBoxCoordinates: { x: boxX, y: boxY },
|
||||||
stageScale,
|
stageScale,
|
||||||
shouldShowCanvasDebugInfo,
|
shouldShowCanvasDebugInfo,
|
||||||
@ -31,10 +28,8 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
let boundingBoxColor = 'inherit';
|
let boundingBoxColor = 'inherit';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(boundingBoxScaleMethod === 'none' &&
|
(boundingBoxScaleMethod === 'none' && (boxWidth < 512 || boxHeight < 512)) ||
|
||||||
(boxWidth < 512 || boxHeight < 512)) ||
|
(boundingBoxScaleMethod === 'manual' && scaledBoxWidth * scaledBoxHeight < 512 * 512)
|
||||||
(boundingBoxScaleMethod === 'manual' &&
|
|
||||||
scaledBoxWidth * scaledBoxHeight < 512 * 512)
|
|
||||||
) {
|
) {
|
||||||
boundingBoxColor = warningColor;
|
boundingBoxColor = warningColor;
|
||||||
}
|
}
|
||||||
@ -45,14 +40,10 @@ const selector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
|||||||
activeLayerColor,
|
activeLayerColor,
|
||||||
layer,
|
layer,
|
||||||
boundingBoxColor,
|
boundingBoxColor,
|
||||||
boundingBoxCoordinatesString: `(${roundToHundreth(boxX)}, ${roundToHundreth(
|
boundingBoxCoordinatesString: `(${roundToHundreth(boxX)}, ${roundToHundreth(boxY)})`,
|
||||||
boxY
|
|
||||||
)})`,
|
|
||||||
boundingBoxDimensionsString: `${boxWidth}×${boxHeight}`,
|
boundingBoxDimensionsString: `${boxWidth}×${boxHeight}`,
|
||||||
scaledBoundingBoxDimensionsString: `${scaledBoxWidth}×${scaledBoxHeight}`,
|
scaledBoundingBoxDimensionsString: `${scaledBoxWidth}×${scaledBoxHeight}`,
|
||||||
canvasCoordinatesString: `${roundToHundreth(stageX)}×${roundToHundreth(
|
canvasCoordinatesString: `${roundToHundreth(stageX)}×${roundToHundreth(stageY)}`,
|
||||||
stageY
|
|
||||||
)}`,
|
|
||||||
canvasDimensionsString: `${stageWidth}×${stageHeight}`,
|
canvasDimensionsString: `${stageWidth}×${stageHeight}`,
|
||||||
canvasScaleString: Math.round(stageScale * 100),
|
canvasScaleString: Math.round(stageScale * 100),
|
||||||
shouldShowCanvasDebugInfo,
|
shouldShowCanvasDebugInfo,
|
||||||
@ -99,9 +90,7 @@ const IAICanvasStatusText = () => {
|
|||||||
bg="base.800"
|
bg="base.800"
|
||||||
>
|
>
|
||||||
<GenerationModeStatusText />
|
<GenerationModeStatusText />
|
||||||
<Box color={activeLayerColor}>{`${t('unifiedCanvas.activeLayer')}: ${t(
|
<Box color={activeLayerColor}>{`${t('unifiedCanvas.activeLayer')}: ${t(`unifiedCanvas.${layer}`)}`}</Box>
|
||||||
`unifiedCanvas.${layer}`
|
|
||||||
)}`}</Box>
|
|
||||||
<Box>{`${t('unifiedCanvas.canvasScale')}: ${canvasScaleString}%`}</Box>
|
<Box>{`${t('unifiedCanvas.canvasScale')}: ${canvasScaleString}%`}</Box>
|
||||||
{shouldPreserveMaskedArea && (
|
{shouldPreserveMaskedArea && (
|
||||||
<Box color={warningColor}>
|
<Box color={warningColor}>
|
||||||
@ -109,9 +98,7 @@ const IAICanvasStatusText = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{shouldShowBoundingBox && (
|
{shouldShowBoundingBox && (
|
||||||
<Box color={boundingBoxColor}>{`${t(
|
<Box color={boundingBoxColor}>{`${t('unifiedCanvas.boundingBox')}: ${boundingBoxDimensionsString}`}</Box>
|
||||||
'unifiedCanvas.boundingBox'
|
|
||||||
)}: ${boundingBoxDimensionsString}`}</Box>
|
|
||||||
)}
|
)}
|
||||||
{shouldShowScaledBoundingBox && (
|
{shouldShowScaledBoundingBox && (
|
||||||
<Box color={boundingBoxColor}>{`${t(
|
<Box color={boundingBoxColor}>{`${t(
|
||||||
@ -120,15 +107,9 @@ const IAICanvasStatusText = () => {
|
|||||||
)}
|
)}
|
||||||
{shouldShowCanvasDebugInfo && (
|
{shouldShowCanvasDebugInfo && (
|
||||||
<>
|
<>
|
||||||
<Box>{`${t(
|
<Box>{`${t('unifiedCanvas.boundingBoxPosition')}: ${boundingBoxCoordinatesString}`}</Box>
|
||||||
'unifiedCanvas.boundingBoxPosition'
|
<Box>{`${t('unifiedCanvas.canvasDimensions')}: ${canvasDimensionsString}`}</Box>
|
||||||
)}: ${boundingBoxCoordinatesString}`}</Box>
|
<Box>{`${t('unifiedCanvas.canvasPosition')}: ${canvasCoordinatesString}`}</Box>
|
||||||
<Box>{`${t(
|
|
||||||
'unifiedCanvas.canvasDimensions'
|
|
||||||
)}: ${canvasDimensionsString}`}</Box>
|
|
||||||
<Box>{`${t(
|
|
||||||
'unifiedCanvas.canvasPosition'
|
|
||||||
)}: ${canvasCoordinatesString}`}</Box>
|
|
||||||
<IAICanvasStatusTextCursorPos />
|
<IAICanvasStatusTextCursorPos />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -14,11 +14,7 @@ const IAICanvasStatusTextCursorPos = () => {
|
|||||||
return `(${roundToHundreth(x)}, ${roundToHundreth(y)})`;
|
return `(${roundToHundreth(x)}, ${roundToHundreth(y)})`;
|
||||||
}, [cursorPosition?.x, cursorPosition?.y]);
|
}, [cursorPosition?.x, cursorPosition?.y]);
|
||||||
|
|
||||||
return (
|
return <Box>{`${t('unifiedCanvas.cursorPosition')}: ${cursorCoordinatesString}`}</Box>;
|
||||||
<Box>{`${t(
|
|
||||||
'unifiedCanvas.cursorPosition'
|
|
||||||
)}: ${cursorCoordinatesString}`}</Box>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(IAICanvasStatusTextCursorPos);
|
export default memo(IAICanvasStatusTextCursorPos);
|
||||||
|
@ -9,23 +9,13 @@ import {
|
|||||||
} from 'features/canvas/store/canvasNanostore';
|
} from 'features/canvas/store/canvasNanostore';
|
||||||
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
import { selectCanvasSlice } from 'features/canvas/store/canvasSlice';
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
import {
|
import { COLOR_PICKER_SIZE, COLOR_PICKER_STROKE_RADIUS } from 'features/canvas/util/constants';
|
||||||
COLOR_PICKER_SIZE,
|
|
||||||
COLOR_PICKER_STROKE_RADIUS,
|
|
||||||
} from 'features/canvas/util/constants';
|
|
||||||
import type { GroupConfig } from 'konva/lib/Group';
|
import type { GroupConfig } from 'konva/lib/Group';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { Circle, Group } from 'react-konva';
|
import { Circle, Group } from 'react-konva';
|
||||||
|
|
||||||
const canvasBrushPreviewSelector = createMemoizedSelector(
|
const canvasBrushPreviewSelector = createMemoizedSelector(selectCanvasSlice, (canvas) => {
|
||||||
selectCanvasSlice,
|
const { stageDimensions, boundingBoxCoordinates, boundingBoxDimensions, shouldRestrictStrokesToBox } = canvas;
|
||||||
(canvas) => {
|
|
||||||
const {
|
|
||||||
stageDimensions,
|
|
||||||
boundingBoxCoordinates,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
shouldRestrictStrokesToBox,
|
|
||||||
} = canvas;
|
|
||||||
|
|
||||||
const clip = shouldRestrictStrokesToBox
|
const clip = shouldRestrictStrokesToBox
|
||||||
? {
|
? {
|
||||||
@ -79,34 +69,24 @@ const canvasBrushPreviewSelector = createMemoizedSelector(
|
|||||||
clip,
|
clip,
|
||||||
stageDimensions,
|
stageDimensions,
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a black circle around the canvas brush preview.
|
* Draws a black circle around the canvas brush preview.
|
||||||
*/
|
*/
|
||||||
const IAICanvasToolPreview = (props: GroupConfig) => {
|
const IAICanvasToolPreview = (props: GroupConfig) => {
|
||||||
const radius = useAppSelector((s) => s.canvas.brushSize / 2);
|
const radius = useAppSelector((s) => s.canvas.brushSize / 2);
|
||||||
const maskColorString = useAppSelector((s) =>
|
const maskColorString = useAppSelector((s) => rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 }));
|
||||||
rgbaColorToString({ ...s.canvas.maskColor, a: 0.5 })
|
|
||||||
);
|
|
||||||
const tool = useStore($tool);
|
const tool = useStore($tool);
|
||||||
const layer = useAppSelector((s) => s.canvas.layer);
|
const layer = useAppSelector((s) => s.canvas.layer);
|
||||||
const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale);
|
const dotRadius = useAppSelector((s) => 1.5 / s.canvas.stageScale);
|
||||||
const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale);
|
const strokeWidth = useAppSelector((s) => 1.5 / s.canvas.stageScale);
|
||||||
const brushColorString = useAppSelector((s) =>
|
const brushColorString = useAppSelector((s) => rgbaColorToString(s.canvas.brushColor));
|
||||||
rgbaColorToString(s.canvas.brushColor)
|
const colorPickerColorString = useAppSelector((s) => rgbaColorToString(s.canvas.colorPickerColor));
|
||||||
);
|
|
||||||
const colorPickerColorString = useAppSelector((s) =>
|
|
||||||
rgbaColorToString(s.canvas.colorPickerColor)
|
|
||||||
);
|
|
||||||
const colorPickerInnerRadius = useAppSelector(
|
const colorPickerInnerRadius = useAppSelector(
|
||||||
(s) =>
|
(s) => (COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / s.canvas.stageScale
|
||||||
(COLOR_PICKER_SIZE - COLOR_PICKER_STROKE_RADIUS + 1) / s.canvas.stageScale
|
|
||||||
);
|
|
||||||
const colorPickerOuterRadius = useAppSelector(
|
|
||||||
(s) => COLOR_PICKER_SIZE / s.canvas.stageScale
|
|
||||||
);
|
);
|
||||||
|
const colorPickerOuterRadius = useAppSelector((s) => COLOR_PICKER_SIZE / s.canvas.stageScale);
|
||||||
const { clip, stageDimensions } = useAppSelector(canvasBrushPreviewSelector);
|
const { clip, stageDimensions } = useAppSelector(canvasBrushPreviewSelector);
|
||||||
|
|
||||||
const cursorPosition = useStore($cursorPosition);
|
const cursorPosition = useStore($cursorPosition);
|
||||||
@ -123,8 +103,7 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const shouldDrawBrushPreview = useMemo(
|
const shouldDrawBrushPreview = useMemo(
|
||||||
() =>
|
() => !(isMovingBoundingBox || isTransformingBoundingBox || !cursorPosition),
|
||||||
!(isMovingBoundingBox || isTransformingBoundingBox || !cursorPosition),
|
|
||||||
[cursorPosition, isMovingBoundingBox, isTransformingBoundingBox]
|
[cursorPosition, isMovingBoundingBox, isTransformingBoundingBox]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -162,9 +141,7 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
|||||||
y={brushY}
|
y={brushY}
|
||||||
radius={radius}
|
radius={radius}
|
||||||
fill={layer === 'mask' ? maskColorString : brushColorString}
|
fill={layer === 'mask' ? maskColorString : brushColorString}
|
||||||
globalCompositeOperation={
|
globalCompositeOperation={tool === 'eraser' ? 'destination-out' : 'source-out'}
|
||||||
tool === 'eraser' ? 'destination-out' : 'source-out'
|
|
||||||
}
|
|
||||||
listening={false}
|
listening={false}
|
||||||
/>
|
/>
|
||||||
<Circle
|
<Circle
|
||||||
@ -187,20 +164,8 @@ const IAICanvasToolPreview = (props: GroupConfig) => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Circle
|
<Circle x={brushX} y={brushY} radius={dotRadius * 2} fill="rgba(255,255,255,0.4)" listening={false} />
|
||||||
x={brushX}
|
<Circle x={brushX} y={brushY} radius={dotRadius} fill="rgba(0,0,0,1)" listening={false} />
|
||||||
y={brushY}
|
|
||||||
radius={dotRadius * 2}
|
|
||||||
fill="rgba(255,255,255,0.4)"
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
<Circle
|
|
||||||
x={brushX}
|
|
||||||
y={brushY}
|
|
||||||
radius={dotRadius}
|
|
||||||
fill="rgba(0,0,0,1)"
|
|
||||||
listening={false}
|
|
||||||
/>
|
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { useShiftModifier } from '@invoke-ai/ui-library';
|
import { useShiftModifier } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { roundDownToMultiple, roundDownToMultipleMin, roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||||
roundDownToMultiple,
|
|
||||||
roundDownToMultipleMin,
|
|
||||||
roundToMultiple,
|
|
||||||
} from 'common/util/roundDownToMultiple';
|
|
||||||
import {
|
import {
|
||||||
$isDrawing,
|
$isDrawing,
|
||||||
$isMouseOverBoundingBox,
|
$isMouseOverBoundingBox,
|
||||||
@ -20,10 +16,7 @@ import {
|
|||||||
setBoundingBoxDimensions,
|
setBoundingBoxDimensions,
|
||||||
setShouldSnapToGrid,
|
setShouldSnapToGrid,
|
||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import {
|
import { CANVAS_GRID_SIZE_COARSE, CANVAS_GRID_SIZE_FINE } from 'features/canvas/store/constants';
|
||||||
CANVAS_GRID_SIZE_COARSE,
|
|
||||||
CANVAS_GRID_SIZE_FINE,
|
|
||||||
} from 'features/canvas/store/constants';
|
|
||||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
@ -41,12 +34,8 @@ type IAICanvasBoundingBoxPreviewProps = GroupConfig;
|
|||||||
const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||||
const { ...rest } = props;
|
const { ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const boundingBoxCoordinates = useAppSelector(
|
const boundingBoxCoordinates = useAppSelector((s) => s.canvas.boundingBoxCoordinates);
|
||||||
(s) => s.canvas.boundingBoxCoordinates
|
const boundingBoxDimensions = useAppSelector((s) => s.canvas.boundingBoxDimensions);
|
||||||
);
|
|
||||||
const boundingBoxDimensions = useAppSelector(
|
|
||||||
(s) => s.canvas.boundingBoxDimensions
|
|
||||||
);
|
|
||||||
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
const stageScale = useAppSelector((s) => s.canvas.stageScale);
|
||||||
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
||||||
const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale);
|
const hitStrokeWidth = useAppSelector((s) => 20 / s.canvas.stageScale);
|
||||||
@ -59,9 +48,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
const isDrawing = useStore($isDrawing);
|
const isDrawing = useStore($isDrawing);
|
||||||
const isMovingBoundingBox = useStore($isMovingBoundingBox);
|
const isMovingBoundingBox = useStore($isMovingBoundingBox);
|
||||||
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
|
const isTransformingBoundingBox = useStore($isTransformingBoundingBox);
|
||||||
const isMouseOverBoundingBoxOutline = useStore(
|
const isMouseOverBoundingBoxOutline = useStore($isMouseOverBoundingBoxOutline);
|
||||||
$isMouseOverBoundingBoxOutline
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!transformerRef.current || !shapeRef.current) {
|
if (!transformerRef.current || !shapeRef.current) {
|
||||||
@ -71,14 +58,8 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
transformerRef.current.getLayer()?.batchDraw();
|
transformerRef.current.getLayer()?.batchDraw();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const gridSize = useMemo(
|
const gridSize = useMemo(() => (shift ? CANVAS_GRID_SIZE_FINE : CANVAS_GRID_SIZE_COARSE), [shift]);
|
||||||
() => (shift ? CANVAS_GRID_SIZE_FINE : CANVAS_GRID_SIZE_COARSE),
|
const scaledStep = useMemo(() => gridSize * stageScale, [gridSize, stageScale]);
|
||||||
[shift]
|
|
||||||
);
|
|
||||||
const scaledStep = useMemo(
|
|
||||||
() => gridSize * stageScale,
|
|
||||||
[gridSize, stageScale]
|
|
||||||
);
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'N',
|
'N',
|
||||||
@ -143,10 +124,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
const y = Math.round(rect.y());
|
const y = Math.round(rect.y());
|
||||||
|
|
||||||
if (aspectRatio.isLocked) {
|
if (aspectRatio.isLocked) {
|
||||||
const newDimensions = calculateNewSize(
|
const newDimensions = calculateNewSize(aspectRatio.value, width * height);
|
||||||
aspectRatio.value,
|
|
||||||
width * height
|
|
||||||
);
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions(
|
setBoundingBoxDimensions(
|
||||||
{
|
{
|
||||||
@ -186,14 +164,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
rect.scaleX(1);
|
rect.scaleX(1);
|
||||||
rect.scaleY(1);
|
rect.scaleY(1);
|
||||||
},
|
},
|
||||||
[
|
[aspectRatio.isLocked, aspectRatio.value, dispatch, shouldSnapToGrid, gridSize, optimalDimension]
|
||||||
aspectRatio.isLocked,
|
|
||||||
aspectRatio.value,
|
|
||||||
dispatch,
|
|
||||||
shouldSnapToGrid,
|
|
||||||
gridSize,
|
|
||||||
optimalDimension,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const anchorDragBoundFunc = useCallback(
|
const anchorDragBoundFunc = useCallback(
|
||||||
@ -255,9 +226,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleMouseOut = useCallback(() => {
|
const handleMouseOut = useCallback(() => {
|
||||||
!isTransformingBoundingBox &&
|
!isTransformingBoundingBox && !isMovingBoundingBox && $isMouseOverBoundingBoxOutline.set(false);
|
||||||
!isMovingBoundingBox &&
|
|
||||||
$isMouseOverBoundingBoxOutline.set(false);
|
|
||||||
}, [isMovingBoundingBox, isTransformingBoundingBox]);
|
}, [isMovingBoundingBox, isTransformingBoundingBox]);
|
||||||
|
|
||||||
const handleMouseEnterBoundingBox = useCallback(() => {
|
const handleMouseEnterBoundingBox = useCallback(() => {
|
||||||
@ -269,35 +238,18 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const stroke = useMemo(() => {
|
const stroke = useMemo(() => {
|
||||||
if (
|
if (isMouseOverBoundingBoxOutline || isMovingBoundingBox || isTransformingBoundingBox) {
|
||||||
isMouseOverBoundingBoxOutline ||
|
|
||||||
isMovingBoundingBox ||
|
|
||||||
isTransformingBoundingBox
|
|
||||||
) {
|
|
||||||
return 'rgba(255,255,255,0.5)';
|
return 'rgba(255,255,255,0.5)';
|
||||||
}
|
}
|
||||||
return 'white';
|
return 'white';
|
||||||
}, [
|
}, [isMouseOverBoundingBoxOutline, isMovingBoundingBox, isTransformingBoundingBox]);
|
||||||
isMouseOverBoundingBoxOutline,
|
|
||||||
isMovingBoundingBox,
|
|
||||||
isTransformingBoundingBox,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const strokeWidth = useMemo(() => {
|
const strokeWidth = useMemo(() => {
|
||||||
if (
|
if (isMouseOverBoundingBoxOutline || isMovingBoundingBox || isTransformingBoundingBox) {
|
||||||
isMouseOverBoundingBoxOutline ||
|
|
||||||
isMovingBoundingBox ||
|
|
||||||
isTransformingBoundingBox
|
|
||||||
) {
|
|
||||||
return 6 / stageScale;
|
return 6 / stageScale;
|
||||||
}
|
}
|
||||||
return 1 / stageScale;
|
return 1 / stageScale;
|
||||||
}, [
|
}, [isMouseOverBoundingBoxOutline, isMovingBoundingBox, isTransformingBoundingBox, stageScale]);
|
||||||
isMouseOverBoundingBoxOutline,
|
|
||||||
isMovingBoundingBox,
|
|
||||||
isTransformingBoundingBox,
|
|
||||||
stageScale,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const enabledAnchors = useMemo(() => {
|
const enabledAnchors = useMemo(() => {
|
||||||
if (tool !== 'move') {
|
if (tool !== 'move') {
|
||||||
|
@ -30,11 +30,7 @@ import { memo, useCallback } from 'react';
|
|||||||
import type { RgbaColor } from 'react-colorful';
|
import type { RgbaColor } from 'react-colorful';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import { PiExcludeBold, PiFloppyDiskBackFill, PiTrashSimpleFill } from 'react-icons/pi';
|
||||||
PiExcludeBold,
|
|
||||||
PiFloppyDiskBackFill,
|
|
||||||
PiTrashSimpleFill,
|
|
||||||
} from 'react-icons/pi';
|
|
||||||
|
|
||||||
const formLabelProps: FormLabelProps = {
|
const formLabelProps: FormLabelProps = {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@ -46,9 +42,7 @@ const IAICanvasMaskOptions = () => {
|
|||||||
const layer = useAppSelector((s) => s.canvas.layer);
|
const layer = useAppSelector((s) => s.canvas.layer);
|
||||||
const maskColor = useAppSelector((s) => s.canvas.maskColor);
|
const maskColor = useAppSelector((s) => s.canvas.maskColor);
|
||||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||||
const shouldPreserveMaskedArea = useAppSelector(
|
const shouldPreserveMaskedArea = useAppSelector((s) => s.canvas.shouldPreserveMaskedArea);
|
||||||
(s) => s.canvas.shouldPreserveMaskedArea
|
|
||||||
);
|
|
||||||
const isStaging = useAppSelector(isStagingSelector);
|
const isStaging = useAppSelector(isStagingSelector);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@ -134,38 +128,21 @@ const IAICanvasMaskOptions = () => {
|
|||||||
<FormControlGroup formLabelProps={formLabelProps}>
|
<FormControlGroup formLabelProps={formLabelProps}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{`${t('unifiedCanvas.enableMask')} (H)`}</FormLabel>
|
<FormLabel>{`${t('unifiedCanvas.enableMask')} (H)`}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={isMaskEnabled} onChange={handleToggleEnableMask} />
|
||||||
isChecked={isMaskEnabled}
|
|
||||||
onChange={handleToggleEnableMask}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.preserveMaskedArea')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.preserveMaskedArea')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldPreserveMaskedArea} onChange={handleChangePreserveMaskedArea} />
|
||||||
isChecked={shouldPreserveMaskedArea}
|
|
||||||
onChange={handleChangePreserveMaskedArea}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormControlGroup>
|
</FormControlGroup>
|
||||||
<Box pt={2} pb={2}>
|
<Box pt={2} pb={2}>
|
||||||
<IAIColorPicker
|
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
|
||||||
color={maskColor}
|
|
||||||
onChange={handleChangeMaskColor}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<ButtonGroup isAttached={false}>
|
<ButtonGroup isAttached={false}>
|
||||||
<Button
|
<Button size="sm" leftIcon={<PiFloppyDiskBackFill />} onClick={handleSaveMask}>
|
||||||
size="sm"
|
|
||||||
leftIcon={<PiFloppyDiskBackFill />}
|
|
||||||
onClick={handleSaveMask}
|
|
||||||
>
|
|
||||||
{t('unifiedCanvas.saveMask')}
|
{t('unifiedCanvas.saveMask')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button size="sm" leftIcon={<PiTrashSimpleFill />} onClick={handleClearMask}>
|
||||||
size="sm"
|
|
||||||
leftIcon={<PiTrashSimpleFill />}
|
|
||||||
onClick={handleClearMask}
|
|
||||||
>
|
|
||||||
{t('unifiedCanvas.clearMask')}
|
{t('unifiedCanvas.clearMask')}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
@ -38,23 +38,13 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const shouldAutoSave = useAppSelector((s) => s.canvas.shouldAutoSave);
|
const shouldAutoSave = useAppSelector((s) => s.canvas.shouldAutoSave);
|
||||||
const shouldCropToBoundingBoxOnSave = useAppSelector(
|
const shouldCropToBoundingBoxOnSave = useAppSelector((s) => s.canvas.shouldCropToBoundingBoxOnSave);
|
||||||
(s) => s.canvas.shouldCropToBoundingBoxOnSave
|
const shouldDarkenOutsideBoundingBox = useAppSelector((s) => s.canvas.shouldDarkenOutsideBoundingBox);
|
||||||
);
|
const shouldShowCanvasDebugInfo = useAppSelector((s) => s.canvas.shouldShowCanvasDebugInfo);
|
||||||
const shouldDarkenOutsideBoundingBox = useAppSelector(
|
|
||||||
(s) => s.canvas.shouldDarkenOutsideBoundingBox
|
|
||||||
);
|
|
||||||
const shouldShowCanvasDebugInfo = useAppSelector(
|
|
||||||
(s) => s.canvas.shouldShowCanvasDebugInfo
|
|
||||||
);
|
|
||||||
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
|
const shouldShowGrid = useAppSelector((s) => s.canvas.shouldShowGrid);
|
||||||
const shouldShowIntermediates = useAppSelector(
|
const shouldShowIntermediates = useAppSelector((s) => s.canvas.shouldShowIntermediates);
|
||||||
(s) => s.canvas.shouldShowIntermediates
|
|
||||||
);
|
|
||||||
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
||||||
const shouldRestrictStrokesToBox = useAppSelector(
|
const shouldRestrictStrokesToBox = useAppSelector((s) => s.canvas.shouldRestrictStrokesToBox);
|
||||||
(s) => s.canvas.shouldRestrictStrokesToBox
|
|
||||||
);
|
|
||||||
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
|
const shouldAntialias = useAppSelector((s) => s.canvas.shouldAntialias);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
@ -70,49 +60,40 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeShouldSnapToGrid = useCallback(
|
const handleChangeShouldSnapToGrid = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldSnapToGrid(e.target.checked)),
|
||||||
dispatch(setShouldSnapToGrid(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeShouldShowIntermediates = useCallback(
|
const handleChangeShouldShowIntermediates = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldShowIntermediates(e.target.checked)),
|
||||||
dispatch(setShouldShowIntermediates(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldShowGrid = useCallback(
|
const handleChangeShouldShowGrid = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldShowGrid(e.target.checked)),
|
||||||
dispatch(setShouldShowGrid(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldDarkenOutsideBoundingBox = useCallback(
|
const handleChangeShouldDarkenOutsideBoundingBox = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)),
|
||||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldAutoSave = useCallback(
|
const handleChangeShouldAutoSave = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldAutoSave(e.target.checked)),
|
||||||
dispatch(setShouldAutoSave(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldCropToBoundingBoxOnSave = useCallback(
|
const handleChangeShouldCropToBoundingBoxOnSave = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)),
|
||||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldRestrictStrokesToBox = useCallback(
|
const handleChangeShouldRestrictStrokesToBox = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldRestrictStrokesToBox(e.target.checked)),
|
||||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldShowCanvasDebugInfo = useCallback(
|
const handleChangeShouldShowCanvasDebugInfo = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldShowCanvasDebugInfo(e.target.checked)),
|
||||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
const handleChangeShouldAntialias = useCallback(
|
const handleChangeShouldAntialias = useCallback(
|
||||||
(e: ChangeEvent<HTMLInputElement>) =>
|
(e: ChangeEvent<HTMLInputElement>) => dispatch(setShouldAntialias(e.target.checked)),
|
||||||
dispatch(setShouldAntialias(e.target.checked)),
|
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -131,29 +112,18 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
<FormControlGroup formLabelProps={formLabelProps}>
|
<FormControlGroup formLabelProps={formLabelProps}>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.showIntermediates')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.showIntermediates')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldShowIntermediates} onChange={handleChangeShouldShowIntermediates} />
|
||||||
isChecked={shouldShowIntermediates}
|
|
||||||
onChange={handleChangeShouldShowIntermediates}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.showGrid')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.showGrid')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldShowGrid} onChange={handleChangeShouldShowGrid} />
|
||||||
isChecked={shouldShowGrid}
|
|
||||||
onChange={handleChangeShouldShowGrid}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.snapToGrid')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.snapToGrid')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldSnapToGrid} onChange={handleChangeShouldSnapToGrid} />
|
||||||
isChecked={shouldSnapToGrid}
|
|
||||||
onChange={handleChangeShouldSnapToGrid}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>
|
<FormLabel>{t('unifiedCanvas.darkenOutsideSelection')}</FormLabel>
|
||||||
{t('unifiedCanvas.darkenOutsideSelection')}
|
|
||||||
</FormLabel>
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={shouldDarkenOutsideBoundingBox}
|
isChecked={shouldDarkenOutsideBoundingBox}
|
||||||
onChange={handleChangeShouldDarkenOutsideBoundingBox}
|
onChange={handleChangeShouldDarkenOutsideBoundingBox}
|
||||||
@ -161,10 +131,7 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.autoSaveToGallery')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.autoSaveToGallery')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldAutoSave} onChange={handleChangeShouldAutoSave} />
|
||||||
isChecked={shouldAutoSave}
|
|
||||||
onChange={handleChangeShouldAutoSave}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.saveBoxRegionOnly')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.saveBoxRegionOnly')}</FormLabel>
|
||||||
@ -175,24 +142,15 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.limitStrokesToBox')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.limitStrokesToBox')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldRestrictStrokesToBox} onChange={handleChangeShouldRestrictStrokesToBox} />
|
||||||
isChecked={shouldRestrictStrokesToBox}
|
|
||||||
onChange={handleChangeShouldRestrictStrokesToBox}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.showCanvasDebugInfo')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.showCanvasDebugInfo')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldShowCanvasDebugInfo} onChange={handleChangeShouldShowCanvasDebugInfo} />
|
||||||
isChecked={shouldShowCanvasDebugInfo}
|
|
||||||
onChange={handleChangeShouldShowCanvasDebugInfo}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormLabel>{t('unifiedCanvas.antialiasing')}</FormLabel>
|
<FormLabel>{t('unifiedCanvas.antialiasing')}</FormLabel>
|
||||||
<Checkbox
|
<Checkbox isChecked={shouldAntialias} onChange={handleChangeShouldAntialias} />
|
||||||
isChecked={shouldAntialias}
|
|
||||||
onChange={handleChangeShouldAntialias}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormControlGroup>
|
</FormControlGroup>
|
||||||
<ClearCanvasHistoryButtonModal />
|
<ClearCanvasHistoryButtonModal />
|
||||||
|
@ -15,17 +15,9 @@ import {
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||||
import {
|
import { $tool, resetToolInteractionState } from 'features/canvas/store/canvasNanostore';
|
||||||
$tool,
|
|
||||||
resetToolInteractionState,
|
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import {
|
import { addEraseRect, addFillRect, setBrushColor, setBrushSize } from 'features/canvas/store/canvasSlice';
|
||||||
addEraseRect,
|
|
||||||
addFillRect,
|
|
||||||
setBrushColor,
|
|
||||||
setBrushSize,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import type { RgbaColor } from 'react-colorful';
|
import type { RgbaColor } from 'react-colorful';
|
||||||
@ -275,11 +267,7 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Box w="full" pt={2} pb={2}>
|
<Box w="full" pt={2} pb={2}>
|
||||||
<IAIColorPicker
|
<IAIColorPicker color={brushColor} onChange={handleChangeBrushColor} withNumberInput />
|
||||||
color={brushColor}
|
|
||||||
onChange={handleChangeBrushColor}
|
|
||||||
withNumberInput
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PopoverBody>
|
</PopoverBody>
|
||||||
|
@ -1,12 +1,5 @@
|
|||||||
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
|
||||||
import {
|
import { ButtonGroup, Combobox, Flex, FormControl, IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||||
ButtonGroup,
|
|
||||||
Combobox,
|
|
||||||
Flex,
|
|
||||||
FormControl,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
} from '@invoke-ai/ui-library';
|
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||||
@ -20,12 +13,7 @@ import {
|
|||||||
} from 'features/canvas/store/actions';
|
} from 'features/canvas/store/actions';
|
||||||
import { $canvasBaseLayer, $tool } from 'features/canvas/store/canvasNanostore';
|
import { $canvasBaseLayer, $tool } from 'features/canvas/store/canvasNanostore';
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import {
|
import { resetCanvas, resetCanvasView, setIsMaskEnabled, setLayer } from 'features/canvas/store/canvasSlice';
|
||||||
resetCanvas,
|
|
||||||
resetCanvasView,
|
|
||||||
setIsMaskEnabled,
|
|
||||||
setLayer,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
|
||||||
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
@ -203,20 +191,13 @@ const IAICanvasToolbar = () => {
|
|||||||
[dispatch, isMaskEnabled]
|
[dispatch, isMaskEnabled]
|
||||||
);
|
);
|
||||||
|
|
||||||
const value = useMemo(
|
const value = useMemo(() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0], [layer]);
|
||||||
() => LAYER_NAMES_DICT.filter((o) => o.value === layer)[0],
|
|
||||||
[layer]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
<Flex alignItems="center" gap={2} flexWrap="wrap">
|
||||||
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
|
||||||
<FormControl isDisabled={isStaging} w="5rem">
|
<FormControl isDisabled={isStaging} w="5rem">
|
||||||
<Combobox
|
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
|
||||||
value={value}
|
|
||||||
options={LAYER_NAMES_DICT}
|
|
||||||
onChange={handleChangeLayer}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { $isMovingBoundingBox, $isMovingStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||||
$isMovingBoundingBox,
|
|
||||||
$isMovingStage,
|
|
||||||
$tool,
|
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { setStageCoordinates } from 'features/canvas/store/canvasSlice';
|
import { setStageCoordinates } from 'features/canvas/store/canvasSlice';
|
||||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||||
@ -13,9 +9,7 @@ const useCanvasDrag = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isStaging = useAppSelector(isStagingSelector);
|
const isStaging = useAppSelector(isStagingSelector);
|
||||||
const handleDragStart = useCallback(() => {
|
const handleDragStart = useCallback(() => {
|
||||||
if (
|
if (!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())) {
|
||||||
!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$isMovingStage.set(true);
|
$isMovingStage.set(true);
|
||||||
@ -36,9 +30,7 @@ const useCanvasDrag = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleDragEnd = useCallback(() => {
|
const handleDragEnd = useCallback(() => {
|
||||||
if (
|
if (!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())) {
|
||||||
!(($tool.get() === 'move' || isStaging) && !$isMovingBoundingBox.get())
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$isMovingStage.set(false);
|
$isMovingStage.set(false);
|
||||||
|
@ -8,30 +8,16 @@ import { useDebounce } from 'react-use';
|
|||||||
export const useCanvasGenerationMode = () => {
|
export const useCanvasGenerationMode = () => {
|
||||||
const layerState = useAppSelector((s) => s.canvas.layerState);
|
const layerState = useAppSelector((s) => s.canvas.layerState);
|
||||||
|
|
||||||
const boundingBoxCoordinates = useAppSelector(
|
const boundingBoxCoordinates = useAppSelector((s) => s.canvas.boundingBoxCoordinates);
|
||||||
(s) => s.canvas.boundingBoxCoordinates
|
const boundingBoxDimensions = useAppSelector((s) => s.canvas.boundingBoxDimensions);
|
||||||
);
|
|
||||||
const boundingBoxDimensions = useAppSelector(
|
|
||||||
(s) => s.canvas.boundingBoxDimensions
|
|
||||||
);
|
|
||||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||||
|
|
||||||
const shouldPreserveMaskedArea = useAppSelector(
|
const shouldPreserveMaskedArea = useAppSelector((s) => s.canvas.shouldPreserveMaskedArea);
|
||||||
(s) => s.canvas.shouldPreserveMaskedArea
|
const [generationMode, setGenerationMode] = useState<GenerationMode | undefined>();
|
||||||
);
|
|
||||||
const [generationMode, setGenerationMode] = useState<
|
|
||||||
GenerationMode | undefined
|
|
||||||
>();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGenerationMode(undefined);
|
setGenerationMode(undefined);
|
||||||
}, [
|
}, [layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea]);
|
||||||
layerState,
|
|
||||||
boundingBoxCoordinates,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
isMaskEnabled,
|
|
||||||
shouldPreserveMaskedArea,
|
|
||||||
]);
|
|
||||||
|
|
||||||
useDebounce(
|
useDebounce(
|
||||||
async () => {
|
async () => {
|
||||||
@ -51,21 +37,12 @@ export const useCanvasGenerationMode = () => {
|
|||||||
const { baseImageData, maskImageData } = canvasBlobsAndImageData;
|
const { baseImageData, maskImageData } = canvasBlobsAndImageData;
|
||||||
|
|
||||||
// Determine the generation mode
|
// Determine the generation mode
|
||||||
const generationMode = getCanvasGenerationMode(
|
const generationMode = getCanvasGenerationMode(baseImageData, maskImageData);
|
||||||
baseImageData,
|
|
||||||
maskImageData
|
|
||||||
);
|
|
||||||
|
|
||||||
setGenerationMode(generationMode);
|
setGenerationMode(generationMode);
|
||||||
},
|
},
|
||||||
1000,
|
1000,
|
||||||
[
|
[layerState, boundingBoxCoordinates, boundingBoxDimensions, isMaskEnabled, shouldPreserveMaskedArea]
|
||||||
layerState,
|
|
||||||
boundingBoxCoordinates,
|
|
||||||
boundingBoxDimensions,
|
|
||||||
isMaskEnabled,
|
|
||||||
shouldPreserveMaskedArea,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return generationMode;
|
return generationMode;
|
||||||
|
@ -22,9 +22,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
const useInpaintingCanvasHotkeys = () => {
|
const useInpaintingCanvasHotkeys = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const shouldShowBoundingBox = useAppSelector(
|
const shouldShowBoundingBox = useAppSelector((s) => s.canvas.shouldShowBoundingBox);
|
||||||
(s) => s.canvas.shouldShowBoundingBox
|
|
||||||
);
|
|
||||||
const isStaging = useAppSelector(isStagingSelector);
|
const isStaging = useAppSelector(isStagingSelector);
|
||||||
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
const isMaskEnabled = useAppSelector((s) => s.canvas.isMaskEnabled);
|
||||||
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
const shouldSnapToGrid = useAppSelector((s) => s.canvas.shouldSnapToGrid);
|
||||||
@ -44,8 +42,7 @@ const useInpaintingCanvasHotkeys = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleEnableMask = () =>
|
const handleToggleEnableMask = () => dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['h'],
|
['h'],
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { $isDrawing, $isMovingStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||||
$isDrawing,
|
|
||||||
$isMovingStage,
|
|
||||||
$tool,
|
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { addLine } from 'features/canvas/store/canvasSlice';
|
import { addLine } from 'features/canvas/store/canvasSlice';
|
||||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { $cursorPosition, $isDrawing, $tool } from 'features/canvas/store/canvasNanostore';
|
||||||
$cursorPosition,
|
|
||||||
$isDrawing,
|
|
||||||
$tool,
|
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
|
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
|
||||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||||
@ -49,17 +45,8 @@ const useCanvasMouseMove = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
didMouseMoveRef.current = true;
|
didMouseMoveRef.current = true;
|
||||||
dispatch(
|
dispatch(addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y]));
|
||||||
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
}, [didMouseMoveRef, dispatch, isStaging, lastCursorPositionRef, stageRef, updateColorUnderCursor]);
|
||||||
);
|
|
||||||
}, [
|
|
||||||
didMouseMoveRef,
|
|
||||||
dispatch,
|
|
||||||
isStaging,
|
|
||||||
lastCursorPositionRef,
|
|
||||||
stageRef,
|
|
||||||
updateColorUnderCursor,
|
|
||||||
]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useCanvasMouseMove;
|
export default useCanvasMouseMove;
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import { $isDrawing, $isMovingStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||||
$isDrawing,
|
|
||||||
$isMovingStage,
|
|
||||||
$tool,
|
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
|
import { addPointToCurrentLine } from 'features/canvas/store/canvasSlice';
|
||||||
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
import getScaledCursorPosition from 'features/canvas/util/getScaledCursorPosition';
|
||||||
@ -39,9 +35,7 @@ const useCanvasMouseUp = (
|
|||||||
* the line's existing points. This allows the line to render as a circle
|
* the line's existing points. This allows the line to render as a circle
|
||||||
* centered on that point.
|
* centered on that point.
|
||||||
*/
|
*/
|
||||||
dispatch(
|
dispatch(addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y]));
|
||||||
addPointToCurrentLine([scaledCursorPosition.x, scaledCursorPosition.y])
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
didMouseMoveRef.current = false;
|
didMouseMoveRef.current = false;
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { $isMoveStageKeyHeld } from 'features/canvas/store/canvasNanostore';
|
import { $isMoveStageKeyHeld } from 'features/canvas/store/canvasNanostore';
|
||||||
import {
|
import { setStageCoordinates, setStageScale } from 'features/canvas/store/canvasSlice';
|
||||||
setStageCoordinates,
|
import { CANVAS_SCALE_BY, MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/canvas/util/constants';
|
||||||
setStageScale,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import {
|
|
||||||
CANVAS_SCALE_BY,
|
|
||||||
MAX_CANVAS_SCALE,
|
|
||||||
MIN_CANVAS_SCALE,
|
|
||||||
} from 'features/canvas/util/constants';
|
|
||||||
import type Konva from 'konva';
|
import type Konva from 'konva';
|
||||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
@ -49,11 +42,7 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
|
|||||||
delta = -delta;
|
delta = -delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newScale = clamp(
|
const newScale = clamp(stageScale * CANVAS_SCALE_BY ** delta, MIN_CANVAS_SCALE, MAX_CANVAS_SCALE);
|
||||||
stageScale * CANVAS_SCALE_BY ** delta,
|
|
||||||
MIN_CANVAS_SCALE,
|
|
||||||
MAX_CANVAS_SCALE
|
|
||||||
);
|
|
||||||
|
|
||||||
const newCoordinates = {
|
const newCoordinates = {
|
||||||
x: cursorPos.x - mousePointTo.x * newScale,
|
x: cursorPos.x - mousePointTo.x * newScale,
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import {
|
import { $canvasBaseLayer, $canvasStage, $tool } from 'features/canvas/store/canvasNanostore';
|
||||||
$canvasBaseLayer,
|
import { commitColorPickerColor, setColorPickerColor } from 'features/canvas/store/canvasSlice';
|
||||||
$canvasStage,
|
|
||||||
$tool,
|
|
||||||
} from 'features/canvas/store/canvasNanostore';
|
|
||||||
import {
|
|
||||||
commitColorPickerColor,
|
|
||||||
setColorPickerColor,
|
|
||||||
} from 'features/canvas/store/canvasSlice';
|
|
||||||
import Konva from 'konva';
|
import Konva from 'konva';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
@ -31,19 +24,9 @@ const useColorPicker = () => {
|
|||||||
|
|
||||||
const [r, g, b, a] = canvasBaseLayer
|
const [r, g, b, a] = canvasBaseLayer
|
||||||
.getContext()
|
.getContext()
|
||||||
.getImageData(
|
.getImageData(position.x * pixelRatio, position.y * pixelRatio, 1, 1).data;
|
||||||
position.x * pixelRatio,
|
|
||||||
position.y * pixelRatio,
|
|
||||||
1,
|
|
||||||
1
|
|
||||||
).data;
|
|
||||||
|
|
||||||
if (
|
if (r === undefined || g === undefined || b === undefined || a === undefined) {
|
||||||
r === undefined ||
|
|
||||||
g === undefined ||
|
|
||||||
b === undefined ||
|
|
||||||
a === undefined
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user