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