chore(ui): format

Lots of changed bc the line length is now 120. May as well do it now.
This commit is contained in:
psychedelicious 2024-01-27 20:55:55 +11:00
parent b922ee566a
commit 189c430e46
568 changed files with 3602 additions and 11172 deletions

View File

@ -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,

View File

@ -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",

View File

@ -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) => {

View File

@ -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>

View File

@ -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>

View File

@ -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';

View File

@ -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;
};

View File

@ -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();

View File

@ -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> = {

View File

@ -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');

View File

@ -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';

View File

@ -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());

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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.

View File

@ -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();

View File

@ -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;
}

View File

@ -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)) {

View File

@ -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'));
},
});
};

View File

@ -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());

View File

@ -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 {

View File

@ -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 {

View File

@ -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' }));
},
});
};

View File

@ -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) {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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`);

View File

@ -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;

View File

@ -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)));
}

View File

@ -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');
},
});
};

View File

@ -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({

View File

@ -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 '..';

View File

@ -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,

View File

@ -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;
}

View File

@ -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;
}
});

View File

@ -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})`);
},
});
};

View File

@ -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;

View File

@ -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');
},
});
};

View File

@ -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());

View File

@ -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,

View File

@ -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})`);
},
});
};

View File

@ -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})`);
},
});
};

View File

@ -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})`);
},
});
};

View File

@ -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}`;

View File

@ -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'])
);
},
});

View File

@ -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})`);
},
});
};

View File

@ -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';

View File

@ -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(

View File

@ -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({

View File

@ -8,6 +8,4 @@ declare global {
}
}
export const $store = atom<
Readonly<ReturnType<typeof createStore>> | undefined
>();
export const $store = atom<Readonly<ReturnType<typeof createStore>> | undefined>();

View File

@ -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']);

View File

@ -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>;

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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>
);

View File

@ -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>
);
};

View File

@ -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';

View File

@ -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>

View File

@ -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[]>(
() =>

View File

@ -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 } }];

View File

@ -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"

View File

@ -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>

View File

@ -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 }));
}
};

View File

@ -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'],

View File

@ -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]
);

View File

@ -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(

View File

@ -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', {

View File

@ -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]
);

View File

@ -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;
}
}

View File

@ -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('-')})`;

View File

@ -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);

View File

@ -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);
};

View File

@ -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

View File

@ -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>

View File

@ -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 (

View File

@ -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} />;

View File

@ -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 (

View File

@ -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) {

View File

@ -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 ||

View File

@ -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>

View File

@ -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) {

View File

@ -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

View File

@ -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}

View File

@ -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 />
</>
)}

View File

@ -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);

View File

@ -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>
);
};

View File

@ -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') {

View File

@ -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>

View File

@ -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 />

View File

@ -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>

View File

@ -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>

View File

@ -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);

View File

@ -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;

View File

@ -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'],

View File

@ -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';

View File

@ -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;

View File

@ -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;
}

View File

@ -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,

View File

@ -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