feat(ui): ux improvements & redesign

This is a squash merge of a bajillion messy small commits created while iterating on the UI component library and redesign.
This commit is contained in:
psychedelicious 2023-12-29 00:03:21 +11:00 committed by Kent Keirsey
parent a47d91f0e7
commit f0b102d830
889 changed files with 16645 additions and 15595 deletions

View File

@ -28,12 +28,14 @@ module.exports = {
'i18next', 'i18next',
'path', 'path',
'unused-imports', 'unused-imports',
'simple-import-sort',
'eslint-plugin-import',
], ],
root: true, root: true,
rules: { rules: {
'path/no-relative-imports': ['error', { maxDepth: 0 }], 'path/no-relative-imports': ['error', { maxDepth: 0 }],
curly: 'error', curly: 'error',
'i18next/no-literal-string': 2, 'i18next/no-literal-string': 'warn',
'react/jsx-no-bind': ['error', { allowBind: true }], 'react/jsx-no-bind': ['error', { allowBind: true }],
'react/jsx-curly-brace-presence': [ 'react/jsx-curly-brace-presence': [
'error', 'error',
@ -43,6 +45,7 @@ module.exports = {
'no-var': 'error', 'no-var': 'error',
'brace-style': 'error', 'brace-style': 'error',
'prefer-template': 'error', 'prefer-template': 'error',
'import/no-duplicates': 'error',
radix: 'error', radix: 'error',
'space-before-blocks': 'error', 'space-before-blocks': 'error',
'import/prefer-default-export': 'off', 'import/prefer-default-export': 'off',
@ -65,7 +68,26 @@ module.exports = {
allowSingleExtends: true, allowSingleExtends: true,
}, },
], ],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'separate-type-imports',
disallowTypeAnnotations: true,
}, },
],
'@typescript-eslint/no-import-type-side-effects': 'error',
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
},
overrides: [
{
files: ['*.stories.tsx'],
rules: {
'i18next/no-literal-string': 'off',
},
},
],
settings: { settings: {
react: { react: {
version: 'detect', version: 'detect',

View File

@ -12,4 +12,5 @@ index.html
src/services/api/schema.d.ts src/services/api/schema.d.ts
static/ static/
src/theme/css/overlayscrollbars.css src/theme/css/overlayscrollbars.css
src/theme_/css/overlayscrollbars.css
pnpm-lock.yaml pnpm-lock.yaml

View File

@ -0,0 +1,23 @@
import { PropsWithChildren, useEffect } from 'react';
import { modelChanged } from '../src/features/parameters/store/generationSlice';
import { useAppDispatch } from '../src/app/store/storeHooks';
import { useGlobalModifiersInit } from '../src/common/hooks/useGlobalModifiers';
/**
* Initializes some state for storybook. Must be in a different component
* so that it is run inside the redux context.
*/
export const ReduxInit = (props: PropsWithChildren) => {
const dispatch = useAppDispatch();
useGlobalModifiersInit();
useEffect(() => {
dispatch(
modelChanged({
model_name: 'test_model',
base_model: 'sd-1',
model_type: 'main',
})
);
}, []);
return props.children;
};

View File

@ -6,6 +6,7 @@ const config: StorybookConfig = {
'@storybook/addon-links', '@storybook/addon-links',
'@storybook/addon-essentials', '@storybook/addon-essentials',
'@storybook/addon-interactions', '@storybook/addon-interactions',
'@storybook/addon-storysource',
], ],
framework: { framework: {
name: '@storybook/react-vite', name: '@storybook/react-vite',

View File

@ -1,16 +1,17 @@
import { Preview } from '@storybook/react'; import { Preview } from '@storybook/react';
import { themes } from '@storybook/theming'; import { themes } from '@storybook/theming';
import i18n from 'i18next'; import i18n from 'i18next';
import React from 'react';
import { initReactI18next } from 'react-i18next'; import { initReactI18next } from 'react-i18next';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import GlobalHotkeys from '../src/app/components/GlobalHotkeys';
import ThemeLocaleProvider from '../src/app/components/ThemeLocaleProvider'; import ThemeLocaleProvider from '../src/app/components/ThemeLocaleProvider';
import { $baseUrl } from '../src/app/store/nanostores/baseUrl';
import { createStore } from '../src/app/store/store'; import { createStore } from '../src/app/store/store';
import { Container } from '@chakra-ui/react';
// TODO: Disabled for IDE performance issues with our translation JSON // TODO: Disabled for IDE performance issues with our translation JSON
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
import translationEN from '../public/locales/en.json'; import translationEN from '../public/locales/en.json';
import { ReduxInit } from './ReduxInit';
i18n.use(initReactI18next).init({ i18n.use(initReactI18next).init({
lng: 'en', lng: 'en',
@ -25,17 +26,21 @@ i18n.use(initReactI18next).init({
}); });
const store = createStore(undefined, false); const store = createStore(undefined, false);
$baseUrl.set('http://localhost:9090');
const preview: Preview = { const preview: Preview = {
decorators: [ decorators: [
(Story) => ( (Story) => {
return (
<Provider store={store}> <Provider store={store}>
<ThemeLocaleProvider> <ThemeLocaleProvider>
<GlobalHotkeys /> <ReduxInit>
<Story /> <Story />
</ReduxInit>
</ThemeLocaleProvider> </ThemeLocaleProvider>
</Provider> </Provider>
), );
},
], ],
parameters: { parameters: {
docs: { docs: {

View File

@ -0,0 +1,15 @@
{
"entry": ["src/main.tsx"],
"extensions": [".ts", ".tsx"],
"ignorePatterns": [
"**/node_modules/**",
"dist/**",
"public/**",
"**/*.stories.tsx",
"config/**"
],
"ignoreUnresolved": [],
"ignoreUnimported": ["src/i18.d.ts", "vite.config.ts", "src/vite-env.d.ts"],
"respectGitignore": true,
"ignoreUnused": []
}

View File

@ -1,6 +1,6 @@
import react from '@vitejs/plugin-react-swc'; import react from '@vitejs/plugin-react-swc';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
import { PluginOption, UserConfig } from 'vite'; import type { PluginOption, UserConfig } from 'vite';
import eslint from 'vite-plugin-eslint'; import eslint from 'vite-plugin-eslint';
import tsconfigPaths from 'vite-tsconfig-paths'; import tsconfigPaths from 'vite-tsconfig-paths';

View File

@ -1,4 +1,5 @@
import { UserConfig } from 'vite'; import type { UserConfig } from 'vite';
import { commonPlugins } from './common'; import { commonPlugins } from './common';
export const appConfig: UserConfig = { export const appConfig: UserConfig = {

View File

@ -1,7 +1,8 @@
import path from 'path'; import path from 'path';
import { UserConfig } from 'vite'; import type { UserConfig } from 'vite';
import dts from 'vite-plugin-dts';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'; import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import dts from 'vite-plugin-dts';
import { commonPlugins } from './common'; import { commonPlugins } from './common';
export const packageConfig: UserConfig = { export const packageConfig: UserConfig = {

View File

@ -31,13 +31,17 @@
"lint": "concurrently -g -n eslint,prettier,tsc,madge -c cyan,green,magenta,yellow \"pnpm run lint:eslint\" \"pnpm run lint:prettier\" \"pnpm run lint:tsc\" \"pnpm run lint:madge\"", "lint": "concurrently -g -n eslint,prettier,tsc,madge -c cyan,green,magenta,yellow \"pnpm run lint:eslint\" \"pnpm run lint:prettier\" \"pnpm run lint:tsc\" \"pnpm run lint:madge\"",
"fix": "eslint --fix . && prettier --log-level warn --write .", "fix": "eslint --fix . && prettier --log-level warn --write .",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"postinstall": "patch-package && pnpm run theme", "postinstall": "pnpm run theme",
"theme": "chakra-cli tokens src/theme/theme.ts", "theme": "chakra-cli tokens src/theme/theme.ts",
"theme:watch": "chakra-cli tokens src/theme/theme.ts --watch", "theme:watch": "chakra-cli tokens src/theme/theme.ts --watch",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"build-storybook": "storybook build" "build-storybook": "storybook build",
"unimported": "npx unimported"
}, },
"madge": { "madge": {
"excludeRegExp": [
"^index.ts$"
],
"detectiveOptions": { "detectiveOptions": {
"ts": { "ts": {
"skipTypeImports": true "skipTypeImports": true
@ -53,6 +57,7 @@
"@chakra-ui/layout": "^2.3.1", "@chakra-ui/layout": "^2.3.1",
"@chakra-ui/portal": "^2.1.0", "@chakra-ui/portal": "^2.1.0",
"@chakra-ui/react": "^2.8.2", "@chakra-ui/react": "^2.8.2",
"@chakra-ui/react-use-size": "^2.1.0",
"@chakra-ui/styled-system": "^2.9.2", "@chakra-ui/styled-system": "^2.9.2",
"@chakra-ui/theme-tools": "^2.1.2", "@chakra-ui/theme-tools": "^2.1.2",
"@dagrejs/graphlib": "^2.1.13", "@dagrejs/graphlib": "^2.1.13",
@ -61,14 +66,11 @@
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@fontsource-variable/inter": "^5.0.16", "@fontsource-variable/inter": "^5.0.16",
"@mantine/core": "^6.0.19",
"@mantine/form": "^6.0.19", "@mantine/form": "^6.0.19",
"@mantine/hooks": "^6.0.19",
"@nanostores/react": "^0.7.1", "@nanostores/react": "^0.7.1",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.0.1",
"@roarr/browser-log-writer": "^1.3.0", "@roarr/browser-log-writer": "^1.3.0",
"@storybook/manager-api": "^7.6.4", "chakra-react-select": "^4.7.6",
"@storybook/theming": "^7.6.4",
"compare-versions": "^6.1.0", "compare-versions": "^6.1.0",
"dateformat": "^5.0.3", "dateformat": "^5.0.3",
"framer-motion": "^10.16.15", "framer-motion": "^10.16.15",
@ -81,7 +83,6 @@
"new-github-issue-url": "^1.0.0", "new-github-issue-url": "^1.0.0",
"overlayscrollbars": "^2.4.5", "overlayscrollbars": "^2.4.5",
"overlayscrollbars-react": "^0.5.3", "overlayscrollbars-react": "^0.5.3",
"patch-package": "^8.0.0",
"query-string": "^8.1.0", "query-string": "^8.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
@ -94,6 +95,8 @@
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-redux": "^9.0.2", "react-redux": "^9.0.2",
"react-resizable-panels": "^0.0.55", "react-resizable-panels": "^0.0.55",
"react-select": "5.7.7",
"react-textarea-autosize": "^8.5.3",
"react-use": "^17.4.2", "react-use": "^17.4.2",
"react-virtuoso": "^4.6.2", "react-virtuoso": "^4.6.2",
"reactflow": "^11.10.1", "reactflow": "^11.10.1",
@ -118,13 +121,17 @@
}, },
"devDependencies": { "devDependencies": {
"@chakra-ui/cli": "^2.4.1", "@chakra-ui/cli": "^2.4.1",
"@storybook/addon-docs": "^7.6.4",
"@storybook/addon-essentials": "^7.6.4", "@storybook/addon-essentials": "^7.6.4",
"@storybook/addon-interactions": "^7.6.4", "@storybook/addon-interactions": "^7.6.4",
"@storybook/addon-links": "^7.6.4", "@storybook/addon-links": "^7.6.4",
"@storybook/addon-storysource": "^7.6.4",
"@storybook/blocks": "^7.6.4", "@storybook/blocks": "^7.6.4",
"@storybook/manager-api": "^7.6.4",
"@storybook/react": "^7.6.4", "@storybook/react": "^7.6.4",
"@storybook/react-vite": "^7.6.4", "@storybook/react-vite": "^7.6.4",
"@storybook/test": "^7.6.4", "@storybook/test": "^7.6.4",
"@storybook/theming": "^7.6.4",
"@types/dateformat": "^5.0.2", "@types/dateformat": "^5.0.2",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
"@types/node": "^20.9.0", "@types/node": "^20.9.0",
@ -138,9 +145,11 @@
"eslint": "^8.55.0", "eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-i18next": "^6.0.3", "eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-path": "^1.2.2", "eslint-plugin-path": "^1.2.2",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^0.6.15", "eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-unused-imports": "^3.0.0", "eslint-plugin-unused-imports": "^3.0.0",
"madge": "^6.1.0", "madge": "^6.1.0",

File diff suppressed because it is too large Load Diff

View File

@ -50,9 +50,33 @@
"uncategorized": "Uncategorized", "uncategorized": "Uncategorized",
"downloadBoard": "Download Board" "downloadBoard": "Download Board"
}, },
"accordions": {
"generation": {
"title": "Generation",
"modelTab": "Model",
"conceptsTab": "Concepts"
},
"image": {
"title": "Image"
},
"advanced": {
"title": "Advanced"
},
"control": {
"title": "Control",
"controlAdaptersTab": "Control Adapters",
"ipTab": "Image Prompts"
},
"compositing": {
"title": "Compositing",
"coherenceTab": "Coherence Pass",
"infillMaskTab": "Infill & Mask"
}
},
"common": { "common": {
"accept": "Accept", "accept": "Accept",
"advanced": "Advanced", "advanced": "Advanced",
"advancedOptions": "Advanced Options",
"ai": "ai", "ai": "ai",
"areYouSure": "Are you sure?", "areYouSure": "Are you sure?",
"auto": "Auto", "auto": "Auto",
@ -79,6 +103,7 @@
"file": "File", "file": "File",
"folder": "Folder", "folder": "Folder",
"format": "format", "format": "format",
"free": "Free",
"generate": "Generate", "generate": "Generate",
"githubLabel": "Github", "githubLabel": "Github",
"hotkeysLabel": "Hotkeys", "hotkeysLabel": "Hotkeys",
@ -221,7 +246,6 @@
"colorMapTileSize": "Tile Size", "colorMapTileSize": "Tile Size",
"importImageFromCanvas": "Import Image From Canvas", "importImageFromCanvas": "Import Image From Canvas",
"importMaskFromCanvas": "Import Mask From Canvas", "importMaskFromCanvas": "Import Mask From Canvas",
"incompatibleBaseModel": "Incompatible base model:",
"lineart": "Lineart", "lineart": "Lineart",
"lineartAnime": "Lineart Anime", "lineartAnime": "Lineart Anime",
"lineartAnimeDescription": "Anime-style lineart processing", "lineartAnimeDescription": "Anime-style lineart processing",
@ -246,6 +270,7 @@
"prompt": "Prompt", "prompt": "Prompt",
"resetControlImage": "Reset Control Image", "resetControlImage": "Reset Control Image",
"resize": "Resize", "resize": "Resize",
"resizeSimple": "Resize (Simple)",
"resizeMode": "Resize Mode", "resizeMode": "Resize Mode",
"safe": "Safe", "safe": "Safe",
"saveControlImage": "Save Control Image", "saveControlImage": "Save Control Image",
@ -284,7 +309,7 @@
"queue": "Queue", "queue": "Queue",
"queueFront": "Add to Front of Queue", "queueFront": "Add to Front of Queue",
"queueBack": "Add to Queue", "queueBack": "Add to Queue",
"queueCountPrediction": "Add {{predicted}} to Queue", "queueCountPrediction": "{{promptsCount}} prompts × {{iterations}} iterations -> {{count}} generations",
"queueMaxExceeded": "Max of {{max_queue_size}} exceeded, would skip {{skip}}", "queueMaxExceeded": "Max of {{max_queue_size}} exceeded, would skip {{skip}}",
"queuedCount": "{{pending}} Pending", "queuedCount": "{{pending}} Pending",
"queueTotal": "{{total}} Total", "queueTotal": "{{total}} Total",
@ -788,17 +813,23 @@
}, },
"models": { "models": {
"addLora": "Add LoRA", "addLora": "Add LoRA",
"allLoRAsAdded": "All LoRAs added",
"loraAlreadyAdded": "LoRA already added",
"esrganModel": "ESRGAN Model", "esrganModel": "ESRGAN Model",
"loading": "loading", "loading": "loading",
"incompatibleBaseModel": "Incompatible base model",
"noMainModelSelected": "No main model selected",
"noLoRAsAvailable": "No LoRAs available", "noLoRAsAvailable": "No LoRAs available",
"noLoRAsLoaded": "No LoRAs Loaded", "noLoRAsLoaded": "No LoRAs Loaded",
"noMatchingLoRAs": "No matching LoRAs", "noMatchingLoRAs": "No matching LoRAs",
"noMatchingModels": "No matching Models", "noMatchingModels": "No matching Models",
"noModelsAvailable": "No models available", "noModelsAvailable": "No models available",
"lora": "LoRA",
"selectLoRA": "Select a LoRA", "selectLoRA": "Select a LoRA",
"selectModel": "Select a Model", "selectModel": "Select a Model",
"noLoRAsInstalled": "No LoRAs installed", "noLoRAsInstalled": "No LoRAs installed",
"noRefinerModelsInstalled": "No SDXL Refiner models installed" "noRefinerModelsInstalled": "No SDXL Refiner models installed",
"defaultVAE": "Default VAE"
}, },
"nodes": { "nodes": {
"addNode": "Add Node", "addNode": "Add Node",
@ -1037,6 +1068,7 @@
"prototypeDesc": "This invocation is a prototype. It may have breaking changes during app updates and may be removed at any time." "prototypeDesc": "This invocation is a prototype. It may have breaking changes during app updates and may be removed at any time."
}, },
"parameters": { "parameters": {
"aspect": "Aspect",
"aspectRatio": "Aspect Ratio", "aspectRatio": "Aspect Ratio",
"aspectRatioFree": "Free", "aspectRatioFree": "Free",
"boundingBoxHeader": "Bounding Box", "boundingBoxHeader": "Bounding Box",
@ -1077,6 +1109,7 @@
"imageFit": "Fit Initial Image To Output Size", "imageFit": "Fit Initial Image To Output Size",
"images": "Images", "images": "Images",
"imageToImage": "Image to Image", "imageToImage": "Image to Image",
"imageSize": "Image Size",
"img2imgStrength": "Image To Image Strength", "img2imgStrength": "Image To Image Strength",
"infillMethod": "Infill Method", "infillMethod": "Infill Method",
"infillScalingHeader": "Infill and Scaling", "infillScalingHeader": "Infill and Scaling",
@ -1127,8 +1160,8 @@
"seamCorrectionHeader": "Seam Correction", "seamCorrectionHeader": "Seam Correction",
"seamHighThreshold": "High", "seamHighThreshold": "High",
"seamlessTiling": "Seamless Tiling", "seamlessTiling": "Seamless Tiling",
"seamlessXAxis": "X Axis", "seamlessXAxis": "Seamless Tiling X Axis",
"seamlessYAxis": "Y Axis", "seamlessYAxis": "Seamless Tiling Y Axis",
"seamlessX": "Seamless X", "seamlessX": "Seamless X",
"seamlessY": "Seamless Y", "seamlessY": "Seamless Y",
"seamlessX&Y": "Seamless X & Y", "seamlessX&Y": "Seamless X & Y",
@ -1171,6 +1204,7 @@
}, },
"dynamicPrompts": { "dynamicPrompts": {
"combinatorial": "Combinatorial Generation", "combinatorial": "Combinatorial Generation",
"showDynamicPrompts": "Show Dynamic Prompts",
"dynamicPrompts": "Dynamic Prompts", "dynamicPrompts": "Dynamic Prompts",
"enableDynamicPrompts": "Enable Dynamic Prompts", "enableDynamicPrompts": "Enable Dynamic Prompts",
"maxPrompts": "Max Prompts", "maxPrompts": "Max Prompts",
@ -1187,7 +1221,8 @@
}, },
"sdxl": { "sdxl": {
"cfgScale": "CFG Scale", "cfgScale": "CFG Scale",
"concatPromptStyle": "Concatenate Prompt & Style", "concatPromptStyle": "Concatenating Prompt & Style",
"freePromptStyle": "Manual Style Prompting",
"denoisingStrength": "Denoising Strength", "denoisingStrength": "Denoising Strength",
"loading": "Loading...", "loading": "Loading...",
"negAestheticScore": "Negative Aesthetic Score", "negAestheticScore": "Negative Aesthetic Score",

View File

@ -1,8 +1,9 @@
import fs from 'node:fs'; import fs from 'node:fs';
import openapiTS from 'openapi-typescript'; import openapiTS from 'openapi-typescript';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json'; const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.d.ts'; const OUTPUT_FILE = 'src/services/api/schema.ts';
async function main() { async function main() {
process.stdout.write( process.stdout.write(

View File

@ -1,13 +1,17 @@
import { Flex, Grid } from '@chakra-ui/react'; import { Flex, Grid } from '@chakra-ui/react';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useSocketIO } from 'app/hooks/useSocketIO';
import { useLogger } from 'app/logging/useLogger'; import { useLogger } from 'app/logging/useLogger';
import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted'; import { appStarted } from 'app/store/middleware/listenerMiddleware/listeners/appStarted';
import { $headerComponent } from 'app/store/nanostores/headerComponent'; import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { PartialAppConfig } from 'app/types/invokeai'; import type { PartialAppConfig } from 'app/types/invokeai';
import ImageUploader from 'common/components/ImageUploader'; import ImageUploader from 'common/components/ImageUploader';
import { useClearStorage } from 'common/hooks/useClearStorage';
import { useGlobalModifiersInit } from 'common/hooks/useGlobalModifiers';
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal'; import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal'; import DeleteImageModal from 'features/deleteImageModal/components/DeleteImageModal';
import { DynamicPromptsModal } from 'features/dynamicPrompts/components/DynamicPromptsPreviewModal';
import SiteHeader from 'features/system/components/SiteHeader'; import SiteHeader from 'features/system/components/SiteHeader';
import { configChanged } from 'features/system/store/configSlice'; import { configChanged } from 'features/system/store/configSlice';
import { languageSelector } from 'features/system/store/systemSelectors'; import { languageSelector } from 'features/system/store/systemSelectors';
@ -16,12 +20,10 @@ import i18n from 'i18n';
import { size } from 'lodash-es'; import { size } from 'lodash-es';
import { memo, useCallback, useEffect } from 'react'; import { memo, useCallback, useEffect } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
import AppErrorBoundaryFallback from './AppErrorBoundaryFallback'; import AppErrorBoundaryFallback from './AppErrorBoundaryFallback';
import GlobalHotkeys from './GlobalHotkeys';
import PreselectedImage from './PreselectedImage'; import PreselectedImage from './PreselectedImage';
import Toaster from './Toaster'; import Toaster from './Toaster';
import { useSocketIO } from 'app/hooks/useSocketIO';
import { useClearStorage } from 'common/hooks/useClearStorage';
const DEFAULT_CONFIG = {}; const DEFAULT_CONFIG = {};
@ -41,6 +43,7 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
// singleton! // singleton!
useSocketIO(); useSocketIO();
useGlobalModifiersInit();
const handleReset = useCallback(() => { const handleReset = useCallback(() => {
clearStorage(); clearStorage();
@ -96,8 +99,8 @@ const App = ({ config = DEFAULT_CONFIG, selectedImage }: Props) => {
</Grid> </Grid>
<DeleteImageModal /> <DeleteImageModal />
<ChangeBoardModal /> <ChangeBoardModal />
<DynamicPromptsModal />
<Toaster /> <Toaster />
<GlobalHotkeys />
<PreselectedImage selectedImage={selectedImage} /> <PreselectedImage selectedImage={selectedImage} />
</ErrorBoundary> </ErrorBoundary>
); );

View File

@ -1,5 +1,6 @@
import { Flex, Heading, Link, Text, useToast } from '@chakra-ui/react'; import { Flex, Heading, Link, useToast } from '@chakra-ui/react';
import IAIButton from 'common/components/IAIButton'; import { InvButton } from 'common/components/InvButton/InvButton';
import { InvText } from 'common/components/InvText/wrapper';
import newGithubIssueUrl from 'new-github-issue-url'; import newGithubIssueUrl from 'new-github-issue-url';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -67,30 +68,24 @@ const AppErrorBoundaryFallback = ({ error, resetErrorBoundary }: Props) => {
alignItems: 'center', alignItems: 'center',
}} }}
> >
<Text <InvText fontWeight="semibold" color="error.400">
sx={{
fontWeight: 600,
color: 'error.500',
_dark: { color: 'error.400' },
}}
>
{error.name}: {error.message} {error.name}: {error.message}
</Text> </InvText>
</Flex> </Flex>
<Flex sx={{ gap: 4 }}> <Flex sx={{ gap: 4 }}>
<IAIButton <InvButton
leftIcon={<FaArrowRotateLeft />} leftIcon={<FaArrowRotateLeft />}
onClick={resetErrorBoundary} onClick={resetErrorBoundary}
> >
{t('accessibility.resetUI')} {t('accessibility.resetUI')}
</IAIButton> </InvButton>
<IAIButton leftIcon={<FaCopy />} onClick={handleCopy}> <InvButton leftIcon={<FaCopy />} onClick={handleCopy}>
{t('common.copyError')} {t('common.copyError')}
</IAIButton> </InvButton>
<Link href={url} isExternal> <Link href={url} isExternal>
<IAIButton leftIcon={<FaExternalLinkAlt />}> <InvButton leftIcon={<FaExternalLinkAlt />}>
{t('accessibility.createIssue')} {t('accessibility.createIssue')}
</IAIButton> </InvButton>
</Link> </Link>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -1,112 +0,0 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useQueueBack } from 'features/queue/hooks/useQueueBack';
import { useQueueFront } from 'features/queue/hooks/useQueueFront';
import {
ctrlKeyPressed,
metaKeyPressed,
shiftKeyPressed,
} from 'features/ui/store/hotkeysSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import React, { memo } from 'react';
import { isHotkeyPressed, useHotkeys } from 'react-hotkeys-hook';
const globalHotkeysSelector = createMemoizedSelector(
[stateSelector],
({ hotkeys }) => {
const { shift, ctrl, meta } = hotkeys;
return { shift, ctrl, meta };
}
);
// TODO: Does not catch keypresses while focused in an input. Maybe there is a way?
/**
* Logical component. Handles app-level global hotkeys.
* @returns null
*/
const GlobalHotkeys: React.FC = () => {
const dispatch = useAppDispatch();
const { shift, ctrl, meta } = useAppSelector(globalHotkeysSelector);
const {
queueBack,
isDisabled: isDisabledQueueBack,
isLoading: isLoadingQueueBack,
} = useQueueBack();
useHotkeys(
['ctrl+enter', 'meta+enter'],
queueBack,
{
enabled: () => !isDisabledQueueBack && !isLoadingQueueBack,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[queueBack, isDisabledQueueBack, isLoadingQueueBack]
);
const {
queueFront,
isDisabled: isDisabledQueueFront,
isLoading: isLoadingQueueFront,
} = useQueueFront();
useHotkeys(
['ctrl+shift+enter', 'meta+shift+enter'],
queueFront,
{
enabled: () => !isDisabledQueueFront && !isLoadingQueueFront,
preventDefault: true,
enableOnFormTags: ['input', 'textarea', 'select'],
},
[queueFront, isDisabledQueueFront, isLoadingQueueFront]
);
useHotkeys(
'*',
() => {
if (isHotkeyPressed('shift')) {
!shift && dispatch(shiftKeyPressed(true));
} else {
shift && dispatch(shiftKeyPressed(false));
}
if (isHotkeyPressed('ctrl')) {
!ctrl && dispatch(ctrlKeyPressed(true));
} else {
ctrl && dispatch(ctrlKeyPressed(false));
}
if (isHotkeyPressed('meta')) {
!meta && dispatch(metaKeyPressed(true));
} else {
meta && dispatch(metaKeyPressed(false));
}
},
{ keyup: true, keydown: true },
[shift, ctrl, meta]
);
useHotkeys('1', () => {
dispatch(setActiveTab('txt2img'));
});
useHotkeys('2', () => {
dispatch(setActiveTab('img2img'));
});
useHotkeys('3', () => {
dispatch(setActiveTab('unifiedCanvas'));
});
useHotkeys('4', () => {
dispatch(setActiveTab('nodes'));
});
useHotkeys('5', () => {
dispatch(setActiveTab('modelManager'));
});
return null;
};
export default memo(GlobalHotkeys);

View File

@ -1,29 +1,25 @@
import { Middleware } from '@reduxjs/toolkit'; import 'i18n';
import type { Middleware } from '@reduxjs/toolkit';
import { $socketOptions } from 'app/hooks/useSocketIO'; import { $socketOptions } from 'app/hooks/useSocketIO';
import { $authToken } from 'app/store/nanostores/authToken'; import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl'; import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $customStarUI, CustomStarUi } from 'app/store/nanostores/customStarUI'; import type { CustomStarUi } from 'app/store/nanostores/customStarUI';
import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { $headerComponent } from 'app/store/nanostores/headerComponent'; import { $headerComponent } from 'app/store/nanostores/headerComponent';
import { $isDebugging } from 'app/store/nanostores/isDebugging'; import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { $projectId } from 'app/store/nanostores/projectId'; import { $projectId } from 'app/store/nanostores/projectId';
import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId'; import { $queueId, DEFAULT_QUEUE_ID } from 'app/store/nanostores/queueId';
import { $store } from 'app/store/nanostores/store'; import { $store } from 'app/store/nanostores/store';
import { createStore } from 'app/store/store'; import { createStore } from 'app/store/store';
import { PartialAppConfig } from 'app/types/invokeai'; import type { PartialAppConfig } from 'app/types/invokeai';
import Loading from 'common/components/Loading/Loading'; import Loading from 'common/components/Loading/Loading';
import AppDndContext from 'features/dnd/components/AppDndContext'; import AppDndContext from 'features/dnd/components/AppDndContext';
import 'i18n'; import type { PropsWithChildren, ReactNode } from 'react';
import React, { import React, { lazy, memo, useEffect, useMemo } from 'react';
PropsWithChildren,
ReactNode,
lazy,
memo,
useEffect,
useMemo,
} from 'react';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares'; import { addMiddleware, resetMiddlewares } from 'redux-dynamic-middlewares';
import { ManagerOptions, SocketOptions } from 'socket.io-client'; import type { ManagerOptions, SocketOptions } from 'socket.io-client';
const App = lazy(() => import('./App')); const App = lazy(() => import('./App'));
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider')); const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));

View File

@ -1,24 +1,17 @@
import {
ChakraProvider,
createLocalStorageManager,
extendTheme,
} from '@chakra-ui/react';
import { ReactNode, memo, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { TOAST_OPTIONS, theme as invokeAITheme } from 'theme/theme';
import '@fontsource-variable/inter'; import '@fontsource-variable/inter';
import { MantineProvider } from '@mantine/core';
import { useMantineTheme } from 'mantine-theme/theme';
import 'overlayscrollbars/overlayscrollbars.css'; import 'overlayscrollbars/overlayscrollbars.css';
import 'theme/css/overlayscrollbars.css'; import 'common/components/OverlayScrollbars/overlayscrollbars.css';
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
import type { ReactNode } from 'react';
import { memo, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { theme as invokeAITheme, TOAST_OPTIONS } from 'theme/theme';
type ThemeLocaleProviderProps = { type ThemeLocaleProviderProps = {
children: ReactNode; children: ReactNode;
}; };
const manager = createLocalStorageManager('@@invokeai-color-mode');
function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) { function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
@ -35,18 +28,10 @@ function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
document.body.dir = direction; document.body.dir = direction;
}, [direction]); }, [direction]);
const mantineTheme = useMantineTheme();
return ( return (
<MantineProvider theme={mantineTheme}> <ChakraProvider theme={theme} toastOptions={TOAST_OPTIONS}>
<ChakraProvider
theme={theme}
colorModeManager={manager}
toastOptions={TOAST_OPTIONS}
>
{children} {children}
</ChakraProvider> </ChakraProvider>
</MantineProvider>
); );
} }

View File

@ -1,7 +1,8 @@
import { useToast } from '@chakra-ui/react'; import { useToast } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { addToast, clearToastQueue } from 'features/system/store/systemSlice'; import { addToast, clearToastQueue } from 'features/system/store/systemSlice';
import { MakeToastArg, makeToast } from 'features/system/util/makeToast'; import type { MakeToastArg } from 'features/system/util/makeToast';
import { makeToast } from 'features/system/util/makeToast';
import { memo, useCallback, useEffect } from 'react'; import { memo, useCallback, useEffect } from 'react';
/** /**

View File

@ -1,94 +0,0 @@
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
type FeatureHelpInfo = {
text: string;
href: string;
guideImage: string;
};
export enum Feature {
PROMPT,
GALLERY,
OTHER,
SEED,
VARIATIONS,
UPSCALE,
FACE_CORRECTION,
IMAGE_TO_IMAGE,
BOUNDING_BOX,
SEAM_CORRECTION,
INFILL_AND_SCALING,
}
/** For each tooltip in the UI, the below feature definitions & props will pull relevant information into the tooltip.
*
* To-do: href & GuideImages are placeholders, and are not currently utilized, but will be updated (along with the tooltip UI) as feature and UI develop and we get a better idea on where things "forever homes" will be .
*/
const useFeatures = (): Record<Feature, FeatureHelpInfo> => {
const { t } = useTranslation();
return useMemo(
() => ({
[Feature.PROMPT]: {
text: t('tooltip.feature.prompt'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.GALLERY]: {
text: t('tooltip.feature.gallery'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.OTHER]: {
text: t('tooltip.feature.other'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.SEED]: {
text: t('tooltip.feature.seed'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.VARIATIONS]: {
text: t('tooltip.feature.variations'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.UPSCALE]: {
text: t('tooltip.feature.upscale'),
href: 'link/to/docs/feature1.html',
guideImage: 'asset/path.gif',
},
[Feature.FACE_CORRECTION]: {
text: t('tooltip.feature.faceCorrection'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.IMAGE_TO_IMAGE]: {
text: t('tooltip.feature.imageToImage'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.BOUNDING_BOX]: {
text: t('tooltip.feature.boundingBox'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.SEAM_CORRECTION]: {
text: t('tooltip.feature.seamCorrection'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
[Feature.INFILL_AND_SCALING]: {
text: t('tooltip.feature.infillAndScaling'),
href: 'link/to/docs/feature3.html',
guideImage: 'asset/path.gif',
},
}),
[t]
);
};
export const useFeatureHelpInfo = (feature: Feature): FeatureHelpInfo => {
const features = useFeatures();
return features[feature];
};

View File

@ -3,14 +3,16 @@ import { $authToken } from 'app/store/nanostores/authToken';
import { $baseUrl } from 'app/store/nanostores/baseUrl'; import { $baseUrl } from 'app/store/nanostores/baseUrl';
import { $isDebugging } from 'app/store/nanostores/isDebugging'; import { $isDebugging } from 'app/store/nanostores/isDebugging';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { MapStore, atom, map } from 'nanostores'; import type { MapStore } from 'nanostores';
import { atom, map } from 'nanostores';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { import type {
ClientToServerEvents, ClientToServerEvents,
ServerToClientEvents, ServerToClientEvents,
} from 'services/events/types'; } from 'services/events/types';
import { setEventListeners } from 'services/events/util/setEventListeners'; import { setEventListeners } from 'services/events/util/setEventListeners';
import { ManagerOptions, Socket, SocketOptions, io } from 'socket.io-client'; import type { ManagerOptions, Socket, SocketOptions } from 'socket.io-client';
import { io } from 'socket.io-client';
// Inject socket options and url into window for debugging // Inject socket options and url into window for debugging
declare global { declare global {

View File

@ -1,6 +1,8 @@
import { createLogWriter } from '@roarr/browser-log-writer'; import { createLogWriter } from '@roarr/browser-log-writer';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import { Logger, ROARR, Roarr } from 'roarr'; import type { Logger } from 'roarr';
import { ROARR, Roarr } from 'roarr';
import { z } from 'zod';
ROARR.write = createLogWriter(); ROARR.write = createLogWriter();
@ -26,19 +28,20 @@ export type LoggerNamespace =
export const logger = (namespace: LoggerNamespace) => export const logger = (namespace: LoggerNamespace) =>
$logger.get().child({ namespace }); $logger.get().child({ namespace });
export const VALID_LOG_LEVELS = [ export const zLogLevel = z.enum([
'trace', 'trace',
'debug', 'debug',
'info', 'info',
'warn', 'warn',
'error', 'error',
'fatal', 'fatal',
] as const; ]);
export type LogLevel = z.infer<typeof zLogLevel>;
export type InvokeLogLevel = (typeof VALID_LOG_LEVELS)[number]; export const isLogLevel = (v: unknown): v is LogLevel =>
zLogLevel.safeParse(v).success;
// Translate human-readable log levels to numbers, used for log filtering // Translate human-readable log levels to numbers, used for log filtering
export const LOG_LEVEL_MAP: Record<InvokeLogLevel, number> = { export const LOG_LEVEL_MAP: Record<LogLevel, number> = {
trace: 10, trace: 10,
debug: 20, debug: 20,
info: 30, info: 30,

View File

@ -4,13 +4,9 @@ import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { ROARR, Roarr } from 'roarr'; import { ROARR, Roarr } from 'roarr';
import {
$logger, import type { LoggerNamespace } from './logger';
BASE_CONTEXT, import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP, logger } from './logger';
LOG_LEVEL_MAP,
LoggerNamespace,
logger,
} from './logger';
const selector = createMemoizedSelector(stateSelector, ({ system }) => { const selector = createMemoizedSelector(stateSelector, ({ system }) => {
const { consoleLogLevel, shouldLogToConsole } = system; const { consoleLogLevel, shouldLogToConsole } = system;

View File

@ -1,6 +1,6 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import { InvokeTabName } from 'features/ui/store/tabMap'; import type { InvokeTabName } from 'features/ui/store/tabMap';
import { BatchConfig } from 'services/api/types'; import type { BatchConfig } from 'services/api/types';
export const enqueueRequested = createAction<{ export const enqueueRequested = createAction<{
tabName: InvokeTabName; tabName: InvokeTabName;

View File

@ -8,7 +8,7 @@ import { postprocessingPersistDenylist } from 'features/parameters/store/postpro
import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist'; import { systemPersistDenylist } from 'features/system/store/systemPersistDenylist';
import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist'; import { uiPersistDenylist } from 'features/ui/store/uiPersistDenylist';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { SerializeFunction } from 'redux-remember'; import type { SerializeFunction } from 'redux-remember';
const serializationDenylist: { const serializationDenylist: {
[key: string]: string[]; [key: string]: string[];

View File

@ -11,7 +11,7 @@ import { initialSystemState } from 'features/system/store/systemSlice';
import { initialHotkeysState } from 'features/ui/store/hotkeysSlice'; import { initialHotkeysState } from 'features/ui/store/hotkeysSlice';
import { initialUIState } from 'features/ui/store/uiSlice'; import { initialUIState } from 'features/ui/store/uiSlice';
import { defaultsDeep } from 'lodash-es'; import { defaultsDeep } from 'lodash-es';
import { UnserializeFunction } from 'redux-remember'; import type { UnserializeFunction } from 'redux-remember';
const initialStates: { const initialStates: {
[key: string]: object; // TODO: type this properly [key: string]: object; // TODO: type this properly

View File

@ -1,8 +1,8 @@
import { UnknownAction } from '@reduxjs/toolkit'; import type { UnknownAction } from '@reduxjs/toolkit';
import { isAnyGraphBuilt } from 'features/nodes/store/actions'; import { isAnyGraphBuilt } from 'features/nodes/store/actions';
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice'; import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { Graph } from 'services/api/types'; import type { Graph } from 'services/api/types';
export const actionSanitizer = <A extends UnknownAction>(action: A): A => { export const actionSanitizer = <A extends UnknownAction>(action: A): A => {
if (isAnyGraphBuilt(action)) { if (isAnyGraphBuilt(action)) {

View File

@ -1,11 +1,12 @@
import type { TypedAddListener, TypedStartListening } from '@reduxjs/toolkit'; import type {
import {
UnknownAction,
ListenerEffect, ListenerEffect,
addListener, TypedAddListener,
createListenerMiddleware, TypedStartListening,
UnknownAction,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import { addListener, createListenerMiddleware } from '@reduxjs/toolkit';
import type { AppDispatch, RootState } from 'app/store/store'; import type { AppDispatch, RootState } from 'app/store/store';
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener'; import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
import { addFirstListImagesListener } from './listeners/addFirstListImagesListener.ts'; import { addFirstListImagesListener } from './listeners/addFirstListImagesListener.ts';
import { addAnyEnqueuedListener } from './listeners/anyEnqueued'; import { addAnyEnqueuedListener } from './listeners/anyEnqueued';
@ -42,13 +43,13 @@ import {
addImageRemovedFromBoardFulfilledListener, addImageRemovedFromBoardFulfilledListener,
addImageRemovedFromBoardRejectedListener, addImageRemovedFromBoardRejectedListener,
} from './listeners/imageRemovedFromBoard'; } from './listeners/imageRemovedFromBoard';
import { addImagesStarredListener } from './listeners/imagesStarred';
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected'; import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
import { import {
addImageUploadedFulfilledListener, addImageUploadedFulfilledListener,
addImageUploadedRejectedListener, addImageUploadedRejectedListener,
} from './listeners/imageUploaded'; } from './listeners/imageUploaded';
import { addImagesStarredListener } from './listeners/imagesStarred';
import { addImagesUnstarredListener } from './listeners/imagesUnstarred';
import { addInitialImageSelectedListener } from './listeners/initialImageSelected'; import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
import { addModelSelectedListener } from './listeners/modelSelected'; import { addModelSelectedListener } from './listeners/modelSelected';
import { addModelsLoadedListener } from './listeners/modelsLoaded'; import { addModelsLoadedListener } from './listeners/modelsLoaded';
@ -69,9 +70,9 @@ import { addSocketSubscribedEventListener as addSocketSubscribedListener } from
import { addSocketUnsubscribedEventListener as addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed'; import { addSocketUnsubscribedEventListener as addSocketUnsubscribedListener } from './listeners/socketio/socketUnsubscribed';
import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSaved'; import { addStagingAreaImageSavedListener } from './listeners/stagingAreaImageSaved';
import { addTabChangedListener } from './listeners/tabChanged'; import { addTabChangedListener } from './listeners/tabChanged';
import { addUpdateAllNodesRequestedListener } from './listeners/updateAllNodesRequested';
import { addUpscaleRequestedListener } from './listeners/upscaleRequested'; import { addUpscaleRequestedListener } from './listeners/upscaleRequested';
import { addWorkflowLoadRequestedListener } from './listeners/workflowLoadRequested'; import { addWorkflowLoadRequestedListener } from './listeners/workflowLoadRequested';
import { addUpdateAllNodesRequestedListener } from './listeners/updateAllNodesRequested';
export const listenerMiddleware = createListenerMiddleware(); export const listenerMiddleware = createListenerMiddleware();

View File

@ -8,6 +8,7 @@ import {
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..'; import { startAppListening } from '..';
const matcher = isAnyOf(commitStagingAreaImage, discardStagedImages); const matcher = isAnyOf(commitStagingAreaImage, discardStagedImages);

View File

@ -2,9 +2,10 @@ import { createAction } from '@reduxjs/toolkit';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import type { ImageCache } from 'services/api/types';
import { getListImagesUrl, imagesAdapter } from 'services/api/util'; import { getListImagesUrl, imagesAdapter } from 'services/api/util';
import { ImageCache } from 'services/api/types';
import { startAppListening } from '..';
export const appStarted = createAction('app/appStarted'); export const appStarted = createAction('app/appStarted');

View File

@ -1,4 +1,5 @@
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addAnyEnqueuedListener = () => { export const addAnyEnqueuedListener = () => {

View File

@ -4,6 +4,7 @@ import {
shouldUseWatermarkerChanged, shouldUseWatermarkerChanged,
} from 'features/system/store/systemSlice'; } from 'features/system/store/systemSlice';
import { appInfoApi } from 'services/api/endpoints/appInfo'; import { appInfoApi } from 'services/api/endpoints/appInfo';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addAppConfigReceivedListener = () => { export const addAppConfigReceivedListener = () => {

View File

@ -1,4 +1,5 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const appStarted = createAction('app/appStarted'); export const appStarted = createAction('app/appStarted');

View File

@ -5,7 +5,8 @@ import { zPydanticValidationError } from 'features/system/store/zodSchemas';
import { t } from 'i18next'; import { t } from 'i18next';
import { truncate, upperFirst } from 'lodash-es'; import { truncate, upperFirst } from 'lodash-es';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { TOAST_OPTIONS, theme } from 'theme/theme'; import { theme, TOAST_OPTIONS } from 'theme/theme';
import { startAppListening } from '..'; import { startAppListening } from '..';
const { toast } = createStandaloneToast({ const { toast } = createStandaloneToast({

View File

@ -4,6 +4,7 @@ import { getImageUsage } from 'features/deleteImageModal/store/selectors';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { clearInitialImage } from 'features/parameters/store/generationSlice'; import { clearInitialImage } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addDeleteBoardAndImagesFulfilledListener = () => { export const addDeleteBoardAndImagesFulfilledListener = () => {

View File

@ -9,9 +9,10 @@ import {
IMAGE_CATEGORIES, IMAGE_CATEGORIES,
} from 'features/gallery/store/types'; } from 'features/gallery/store/types';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { imagesSelectors } from 'services/api/util'; import { imagesSelectors } from 'services/api/util';
import { startAppListening } from '..';
export const addBoardIdSelectedListener = () => { export const addBoardIdSelectedListener = () => {
startAppListening({ startAppListening({
matcher: isAnyOf(boardIdSelected, galleryViewChanged), matcher: isAnyOf(boardIdSelected, galleryViewChanged),

View File

@ -1,11 +1,12 @@
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { startAppListening } from '..';
import { $logger } from 'app/logging/logger'; import { $logger } from 'app/logging/logger';
import { canvasCopiedToClipboard } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard'; import { copyBlobToClipboard } from 'features/system/util/copyBlobToClipboard';
import { t } from 'i18next'; import { t } from 'i18next';
import { startAppListening } from '..';
export const addCanvasCopiedToClipboardListener = () => { export const addCanvasCopiedToClipboardListener = () => {
startAppListening({ startAppListening({
actionCreator: canvasCopiedToClipboard, actionCreator: canvasCopiedToClipboard,

View File

@ -1,11 +1,12 @@
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { startAppListening } from '..';
import { $logger } from 'app/logging/logger'; import { $logger } from 'app/logging/logger';
import { canvasDownloadedAsImage } from 'features/canvas/store/actions';
import { downloadBlob } from 'features/canvas/util/downloadBlob'; import { downloadBlob } from 'features/canvas/util/downloadBlob';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { startAppListening } from '..';
export const addCanvasDownloadedAsImageListener = () => { export const addCanvasDownloadedAsImageListener = () => {
startAppListening({ startAppListening({
actionCreator: canvasDownloadedAsImage, actionCreator: canvasDownloadedAsImage,

View File

@ -1,11 +1,12 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice'; import { controlAdapterImageChanged } from 'features/controlAdapters/store/controlAdaptersSlice';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { canvasImageToControlAdapter } from 'features/canvas/store/actions';
export const addCanvasImageToControlNetListener = () => { export const addCanvasImageToControlNetListener = () => {
startAppListening({ startAppListening({

View File

@ -2,9 +2,10 @@ import { logger } from 'app/logging/logger';
import { canvasMaskSavedToGallery } from 'features/canvas/store/actions'; import { canvasMaskSavedToGallery } from 'features/canvas/store/actions';
import { getCanvasData } from 'features/canvas/util/getCanvasData'; import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasMaskSavedToGalleryListener = () => { export const addCanvasMaskSavedToGalleryListener = () => {
startAppListening({ startAppListening({

View File

@ -5,6 +5,7 @@ import { controlAdapterImageChanged } from 'features/controlAdapters/store/contr
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addCanvasMaskToControlNetListener = () => { export const addCanvasMaskToControlNetListener = () => {

View File

@ -4,9 +4,10 @@ import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob'; import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasMergedListener = () => { export const addCanvasMergedListener = () => {
startAppListening({ startAppListening({

View File

@ -2,9 +2,10 @@ import { logger } from 'app/logging/logger';
import { canvasSavedToGallery } from 'features/canvas/store/actions'; import { canvasSavedToGallery } from 'features/canvas/store/actions';
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob'; import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addCanvasSavedToGalleryListener = () => { export const addCanvasSavedToGalleryListener = () => {
startAppListening({ startAppListening({

View File

@ -1,6 +1,6 @@
import { AnyListenerPredicate } from '@reduxjs/toolkit'; import type { AnyListenerPredicate } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions'; import { controlAdapterImageProcessed } from 'features/controlAdapters/store/actions';
import { import {
controlAdapterAutoConfigToggled, controlAdapterAutoConfigToggled,
@ -10,9 +10,10 @@ import {
controlAdapterProcessortTypeChanged, controlAdapterProcessortTypeChanged,
selectControlAdapterById, selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { startAppListening } from '..';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { startAppListening } from '..';
type AnyControlAdapterParamChangeAction = type AnyControlAdapterParamChangeAction =
| ReturnType<typeof controlAdapterProcessorParamsChanged> | ReturnType<typeof controlAdapterProcessorParamsChanged>
| ReturnType<typeof controlAdapterModelChanged> | ReturnType<typeof controlAdapterModelChanged>

View File

@ -8,14 +8,15 @@ import {
selectControlAdapterById, selectControlAdapterById,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types'; import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
import { isImageOutput } from 'features/nodes/types/common';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { BatchConfig, ImageDTO } from 'services/api/types'; import type { BatchConfig, ImageDTO } from 'services/api/types';
import { socketInvocationComplete } from 'services/events/actions'; import { socketInvocationComplete } from 'services/events/actions';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { isImageOutput } from 'features/nodes/types/common';
export const addControlNetImageProcessedListener = () => { export const addControlNetImageProcessedListener = () => {
startAppListening({ startAppListening({

View File

@ -14,7 +14,8 @@ import { buildCanvasGraph } from 'features/nodes/util/graph/buildCanvasGraph';
import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { ImageDTO } from 'services/api/types'; import type { ImageDTO } from 'services/api/types';
import { startAppListening } from '..'; import { startAppListening } from '..';
/** /**

View File

@ -5,6 +5,7 @@ import { buildLinearSDXLImageToImageGraph } from 'features/nodes/util/graph/buil
import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph'; import { buildLinearSDXLTextToImageGraph } from 'features/nodes/util/graph/buildLinearSDXLTextToImageGraph';
import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph'; import { buildLinearTextToImageGraph } from 'features/nodes/util/graph/buildLinearTextToImageGraph';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addEnqueueRequestedLinear = () => { export const addEnqueueRequestedLinear = () => {

View File

@ -2,7 +2,8 @@ import { enqueueRequested } from 'app/store/actions';
import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph'; import { buildNodesGraph } from 'features/nodes/util/graph/buildNodesGraph';
import { buildWorkflow } from 'features/nodes/util/workflow/buildWorkflow'; import { buildWorkflow } from 'features/nodes/util/workflow/buildWorkflow';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import { BatchConfig } from 'services/api/types'; import type { BatchConfig } from 'services/api/types';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addEnqueueRequestedNodes = () => { export const addEnqueueRequestedNodes = () => {

View File

@ -1,5 +1,6 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addImageAddedToBoardFulfilledListener = () => { export const addImageAddedToBoardFulfilledListener = () => {

View File

@ -18,6 +18,7 @@ import { clamp, forEach } from 'lodash-es';
import { api } from 'services/api'; import { api } from 'services/api';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { imagesAdapter } from 'services/api/util'; import { imagesAdapter } from 'services/api/util';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addRequestedSingleImageDeletionListener = () => { export const addRequestedSingleImageDeletionListener = () => {

View File

@ -6,16 +6,17 @@ import {
controlAdapterImageChanged, controlAdapterImageChanged,
controlAdapterIsEnabledChanged, controlAdapterIsEnabledChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { import type {
TypesafeDraggableData, TypesafeDraggableData,
TypesafeDroppableData, TypesafeDroppableData,
} from 'features/dnd/types'; } from 'features/dnd/types';
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '../'; import { startAppListening } from '../';
import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice';
export const dndDropped = createAction<{ export const dndDropped = createAction<{
overData: TypesafeDroppableData; overData: TypesafeDroppableData;

View File

@ -1,5 +1,6 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addImageRemovedFromBoardFulfilledListener = () => { export const addImageRemovedFromBoardFulfilledListener = () => {

View File

@ -4,6 +4,7 @@ import {
imagesToDeleteSelected, imagesToDeleteSelected,
isModalOpenChanged, isModalOpenChanged,
} from 'features/deleteImageModal/store/slice'; } from 'features/deleteImageModal/store/slice';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addImageToDeleteSelectedListener = () => { export const addImageToDeleteSelectedListener = () => {

View File

@ -1,4 +1,4 @@
import { UseToastOptions } from '@chakra-ui/react'; import type { UseToastOptions } from '@chakra-ui/react';
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { import {
@ -11,9 +11,10 @@ import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards'; import { boardsApi } from 'services/api/endpoints/boards';
import { startAppListening } from '..';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addImageUploadedFulfilledListener = () => { export const addImageUploadedFulfilledListener = () => {
startAppListening({ startAppListening({
matcher: imagesApi.endpoints.uploadImage.matchFulfilled, matcher: imagesApi.endpoints.uploadImage.matchFulfilled,

View File

@ -1,7 +1,8 @@
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { selectionChanged } from 'features/gallery/store/gallerySlice'; import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { ImageDTO } from 'services/api/types'; import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { startAppListening } from '..';
export const addImagesStarredListener = () => { export const addImagesStarredListener = () => {
startAppListening({ startAppListening({

View File

@ -1,7 +1,8 @@
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { selectionChanged } from 'features/gallery/store/gallerySlice'; import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { ImageDTO } from 'services/api/types'; import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types';
import { startAppListening } from '..';
export const addImagesUnstarredListener = () => { export const addImagesUnstarredListener = () => {
startAppListening({ startAppListening({

View File

@ -3,6 +3,7 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast'; import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addInitialImageSelectedListener = () => { export const addInitialImageSelectedListener = () => {

View File

@ -7,17 +7,18 @@ import {
import { loraRemoved } from 'features/lora/store/loraSlice'; import { loraRemoved } from 'features/lora/store/loraSlice';
import { modelSelected } from 'features/parameters/store/actions'; import { modelSelected } from 'features/parameters/store/actions';
import { import {
heightChanged,
modelChanged, modelChanged,
setHeight,
setWidth,
vaeSelected, vaeSelected,
widthChanged,
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast'; import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { forEach } from 'lodash-es'; import { forEach } from 'lodash-es';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
export const addModelSelectedListener = () => { export const addModelSelectedListener = () => {
startAppListening({ startAppListening({
@ -89,12 +90,12 @@ export const addModelSelectedListener = () => {
state.ui.shouldAutoChangeDimensions state.ui.shouldAutoChangeDimensions
) { ) {
if (['sdxl', 'sdxl-refiner'].includes(newModel.base_model)) { if (['sdxl', 'sdxl-refiner'].includes(newModel.base_model)) {
dispatch(setWidth(1024)); dispatch(widthChanged(1024));
dispatch(setHeight(1024)); dispatch(heightChanged(1024));
dispatch(setBoundingBoxDimensions({ width: 1024, height: 1024 })); dispatch(setBoundingBoxDimensions({ width: 1024, height: 1024 }));
} else { } else {
dispatch(setWidth(512)); dispatch(widthChanged(512));
dispatch(setHeight(512)); dispatch(heightChanged(512));
dispatch(setBoundingBoxDimensions({ width: 512, height: 512 })); dispatch(setBoundingBoxDimensions({ width: 512, height: 512 }));
} }
} }

View File

@ -12,20 +12,17 @@ import {
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { import {
zParameterModel, zParameterModel,
zParameterSDXLRefinerModel,
zParameterVAEModel, zParameterVAEModel,
} from 'features/parameters/types/parameterSchemas'; } from 'features/parameters/types/parameterSchemas';
import { import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
refinerModelChanged,
setShouldUseSDXLRefiner,
} from 'features/sdxl/store/sdxlSlice';
import { forEach, some } from 'lodash-es'; import { forEach, some } from 'lodash-es';
import { import {
mainModelsAdapter, mainModelsAdapter,
modelsApi, modelsApi,
vaeModelsAdapter, vaeModelsAdapter,
} from 'services/api/endpoints/models'; } from 'services/api/endpoints/models';
import { TypeGuardFor } from 'services/api/types'; import type { TypeGuardFor } from 'services/api/types';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addModelsLoadedListener = () => { export const addModelsLoadedListener = () => {
@ -102,7 +99,6 @@ export const addModelsLoadedListener = () => {
if (models.length === 0) { if (models.length === 0) {
// No models loaded at all // No models loaded at all
dispatch(refinerModelChanged(null)); dispatch(refinerModelChanged(null));
dispatch(setShouldUseSDXLRefiner(false));
return; return;
} }
@ -115,21 +111,10 @@ export const addModelsLoadedListener = () => {
) )
: false; : false;
if (isCurrentModelAvailable) { if (!isCurrentModelAvailable) {
dispatch(refinerModelChanged(null));
return; return;
} }
const result = zParameterSDXLRefinerModel.safeParse(models[0]);
if (!result.success) {
log.error(
{ error: result.error.format() },
'Failed to parse SDXL Refiner Model'
);
return;
}
dispatch(refinerModelChanged(result.data));
}, },
}); });
startAppListening({ startAppListening({

View File

@ -11,6 +11,7 @@ import {
import { setPositivePrompt } from 'features/parameters/store/generationSlice'; import { setPositivePrompt } from 'features/parameters/store/generationSlice';
import { utilitiesApi } from 'services/api/endpoints/utilities'; import { utilitiesApi } from 'services/api/endpoints/utilities';
import { appSocketConnected } from 'services/events/actions'; import { appSocketConnected } from 'services/events/actions';
import { startAppListening } from '..'; import { startAppListening } from '..';
const matcher = isAnyOf( const matcher = isAnyOf(

View File

@ -4,6 +4,7 @@ import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
import { parseSchema } from 'features/nodes/util/schema/parseSchema'; import { parseSchema } from 'features/nodes/util/schema/parseSchema';
import { size } from 'lodash-es'; import { size } from 'lodash-es';
import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addReceivedOpenAPISchemaListener = () => { export const addReceivedOpenAPISchemaListener = () => {

View File

@ -1,10 +1,11 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { isInitializedChanged } from 'features/system/store/systemSlice';
import { size } from 'lodash-es'; import { size } from 'lodash-es';
import { api } from 'services/api'; import { api } from 'services/api';
import { receivedOpenAPISchema } from 'services/api/thunks/schema'; import { receivedOpenAPISchema } from 'services/api/thunks/schema';
import { appSocketConnected, socketConnected } from 'services/events/actions'; import { appSocketConnected, socketConnected } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
import { isInitializedChanged } from 'features/system/store/systemSlice';
export const addSocketConnectedEventListener = () => { export const addSocketConnectedEventListener = () => {
startAppListening({ startAppListening({

View File

@ -3,6 +3,7 @@ import {
appSocketDisconnected, appSocketDisconnected,
socketDisconnected, socketDisconnected,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addSocketDisconnectedEventListener = () => { export const addSocketDisconnectedEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketGeneratorProgress, appSocketGeneratorProgress,
socketGeneratorProgress, socketGeneratorProgress,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addGeneratorProgressEventListener = () => { export const addGeneratorProgressEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketGraphExecutionStateComplete, appSocketGraphExecutionStateComplete,
socketGraphExecutionStateComplete, socketGraphExecutionStateComplete,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addGraphExecutionStateCompleteEventListener = () => { export const addGraphExecutionStateCompleteEventListener = () => {

View File

@ -7,6 +7,7 @@ import {
imageSelected, imageSelected,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { IMAGE_CATEGORIES } from 'features/gallery/store/types'; import { IMAGE_CATEGORIES } from 'features/gallery/store/types';
import { isImageOutput } from 'features/nodes/types/common';
import { import {
LINEAR_UI_OUTPUT, LINEAR_UI_OUTPUT,
nodeIDDenyList, nodeIDDenyList,
@ -18,8 +19,8 @@ import {
appSocketInvocationComplete, appSocketInvocationComplete,
socketInvocationComplete, socketInvocationComplete,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
import { isImageOutput } from 'features/nodes/types/common';
// These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them // These nodes output an image, but do not actually *save* an image, so we don't want to handle the gallery logic on them
const nodeTypeDenylist = ['load_image', 'image']; const nodeTypeDenylist = ['load_image', 'image'];

View File

@ -3,6 +3,7 @@ import {
appSocketInvocationError, appSocketInvocationError,
socketInvocationError, socketInvocationError,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addInvocationErrorEventListener = () => { export const addInvocationErrorEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketInvocationRetrievalError, appSocketInvocationRetrievalError,
socketInvocationRetrievalError, socketInvocationRetrievalError,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addInvocationRetrievalErrorEventListener = () => { export const addInvocationRetrievalErrorEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketInvocationStarted, appSocketInvocationStarted,
socketInvocationStarted, socketInvocationStarted,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addInvocationStartedEventListener = () => { export const addInvocationStartedEventListener = () => {

View File

@ -5,6 +5,7 @@ import {
socketModelLoadCompleted, socketModelLoadCompleted,
socketModelLoadStarted, socketModelLoadStarted,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addModelLoadEventListener = () => { export const addModelLoadEventListener = () => {

View File

@ -4,6 +4,7 @@ import {
appSocketQueueItemStatusChanged, appSocketQueueItemStatusChanged,
socketQueueItemStatusChanged, socketQueueItemStatusChanged,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addSocketQueueItemStatusChangedEventListener = () => { export const addSocketQueueItemStatusChangedEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketSessionRetrievalError, appSocketSessionRetrievalError,
socketSessionRetrievalError, socketSessionRetrievalError,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addSessionRetrievalErrorEventListener = () => { export const addSessionRetrievalErrorEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketSubscribedSession, appSocketSubscribedSession,
socketSubscribedSession, socketSubscribedSession,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addSocketSubscribedEventListener = () => { export const addSocketSubscribedEventListener = () => {

View File

@ -3,6 +3,7 @@ import {
appSocketUnsubscribedSession, appSocketUnsubscribedSession,
socketUnsubscribedSession, socketUnsubscribedSession,
} from 'services/events/actions'; } from 'services/events/actions';
import { startAppListening } from '../..'; import { startAppListening } from '../..';
export const addSocketUnsubscribedEventListener = () => { export const addSocketUnsubscribedEventListener = () => {

View File

@ -1,8 +1,9 @@
import { stagingAreaImageSaved } from 'features/canvas/store/actions'; import { stagingAreaImageSaved } from 'features/canvas/store/actions';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
import { t } from 'i18next'; import { t } from 'i18next';
import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '..';
export const addStagingAreaImageSavedListener = () => { export const addStagingAreaImageSavedListener = () => {
startAppListening({ startAppListening({

View File

@ -2,6 +2,7 @@ import { modelChanged } from 'features/parameters/store/generationSlice';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { NON_REFINER_BASE_MODELS } from 'services/api/constants'; import { NON_REFINER_BASE_MODELS } from 'services/api/constants';
import { mainModelsAdapter, modelsApi } from 'services/api/endpoints/models'; import { mainModelsAdapter, modelsApi } from 'services/api/endpoints/models';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addTabChangedListener = () => { export const addTabChangedListener = () => {

View File

@ -1,15 +1,16 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { updateAllNodesRequested } from 'features/nodes/store/actions'; import { updateAllNodesRequested } from 'features/nodes/store/actions';
import { nodeReplaced } from 'features/nodes/store/nodesSlice'; import { nodeReplaced } from 'features/nodes/store/nodesSlice';
import { NodeUpdateError } from 'features/nodes/types/error';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { import {
getNeedsUpdate, getNeedsUpdate,
updateNode, updateNode,
} from 'features/nodes/util/node/nodeUpdate'; } from 'features/nodes/util/node/nodeUpdate';
import { NodeUpdateError } from 'features/nodes/types/error';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { makeToast } from 'features/system/util/makeToast'; import { makeToast } from 'features/system/util/makeToast';
import { t } from 'i18next'; import { t } from 'i18next';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addUpdateAllNodesRequestedListener = () => { export const addUpdateAllNodesRequestedListener = () => {

View File

@ -2,12 +2,13 @@ import { createAction } from '@reduxjs/toolkit';
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph'; import { buildAdHocUpscaleGraph } from 'features/nodes/util/graph/buildAdHocUpscaleGraph';
import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { queueApi } from 'services/api/endpoints/queue'; import { queueApi } from 'services/api/endpoints/queue';
import type { BatchConfig, ImageDTO } from 'services/api/types';
import { startAppListening } from '..'; import { startAppListening } from '..';
import { BatchConfig, ImageDTO } from 'services/api/types';
import { createIsAllowedToUpscaleSelector } from 'features/parameters/hooks/useIsAllowedToUpscale';
export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>( export const upscaleRequested = createAction<{ imageDTO: ImageDTO }>(
`upscale/upscaleRequested` `upscale/upscaleRequested`

View File

@ -1,7 +1,9 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import {
import { workflowLoaded } from 'features/nodes/store/actions'; workflowLoaded,
workflowLoadRequested,
} from 'features/nodes/store/actions';
import { $flow } from 'features/nodes/store/reactFlowInstance'; import { $flow } from 'features/nodes/store/reactFlowInstance';
import { import {
WorkflowMigrationError, WorkflowMigrationError,
@ -14,6 +16,7 @@ import { setActiveTab } from 'features/ui/store/uiSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { z } from 'zod'; import { z } from 'zod';
import { fromZodError } from 'zod-validation-error'; import { fromZodError } from 'zod-validation-error';
import { startAppListening } from '..'; import { startAppListening } from '..';
export const addWorkflowLoadRequestedListener = () => { export const addWorkflowLoadRequestedListener = () => {

View File

@ -0,0 +1,3 @@
# nanostores
For non-serializable data that needs to be available throughout the app, or when redux is not appropriate, use nanostores.

View File

@ -1,4 +1,4 @@
import { MenuItemProps } from '@chakra-ui/react'; import type { MenuItemProps } from '@chakra-ui/react';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
export type CustomStarUi = { export type CustomStarUi = {

View File

@ -1,4 +1,4 @@
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import { ReactNode } from 'react'; import type { ReactNode } from 'react';
export const $headerComponent = atom<ReactNode | undefined>(undefined); export const $headerComponent = atom<ReactNode | undefined>(undefined);

View File

@ -1,3 +0,0 @@
/**
* For non-serializable data that needs to be available throughout the app, or when redux is not appropriate, use nanostores.
*/

View File

@ -1,4 +1,4 @@
import { createStore } from 'app/store/store'; import type { createStore } from 'app/store/store';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
export const $store = atom< export const $store = atom<

View File

@ -1,6 +1,5 @@
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
import { import {
ThunkDispatch,
UnknownAction,
autoBatchEnhancer, autoBatchEnhancer,
combineReducers, combineReducers,
configureStore, configureStore,
@ -11,6 +10,7 @@ import controlAdaptersReducer from 'features/controlAdapters/store/controlAdapte
import deleteImageModalReducer from 'features/deleteImageModal/store/slice'; import deleteImageModalReducer from 'features/deleteImageModal/store/slice';
import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice';
import galleryReducer from 'features/gallery/store/gallerySlice'; import galleryReducer from 'features/gallery/store/gallerySlice';
import hrfReducer from 'features/hrf/store/hrfSlice';
import loraReducer from 'features/lora/store/loraSlice'; import loraReducer from 'features/lora/store/loraSlice';
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice'; import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
import nodesReducer from 'features/nodes/store/nodesSlice'; import nodesReducer from 'features/nodes/store/nodesSlice';
@ -21,12 +21,14 @@ import queueReducer from 'features/queue/store/queueSlice';
import sdxlReducer from 'features/sdxl/store/sdxlSlice'; import sdxlReducer from 'features/sdxl/store/sdxlSlice';
import configReducer from 'features/system/store/configSlice'; import configReducer from 'features/system/store/configSlice';
import systemReducer from 'features/system/store/systemSlice'; import systemReducer from 'features/system/store/systemSlice';
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
import uiReducer from 'features/ui/store/uiSlice'; import uiReducer from 'features/ui/store/uiSlice';
import { createStore as createIDBKeyValStore, get, set } from 'idb-keyval'; import { createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
import dynamicMiddlewares from 'redux-dynamic-middlewares'; import dynamicMiddlewares from 'redux-dynamic-middlewares';
import { Driver, rememberEnhancer, rememberReducer } from 'redux-remember'; import type { Driver } from 'redux-remember';
import { rememberEnhancer, rememberReducer } from 'redux-remember';
import { api } from 'services/api'; import { api } from 'services/api';
import { authToastMiddleware } from 'services/api/authToastMiddleware';
import { STORAGE_PREFIX } from './constants'; import { STORAGE_PREFIX } from './constants';
import { serialize } from './enhancers/reduxRemember/serialize'; import { serialize } from './enhancers/reduxRemember/serialize';
import { unserialize } from './enhancers/reduxRemember/unserialize'; import { unserialize } from './enhancers/reduxRemember/unserialize';
@ -34,7 +36,6 @@ import { actionSanitizer } from './middleware/devtools/actionSanitizer';
import { actionsDenylist } from './middleware/devtools/actionsDenylist'; import { actionsDenylist } from './middleware/devtools/actionsDenylist';
import { stateSanitizer } from './middleware/devtools/stateSanitizer'; import { stateSanitizer } from './middleware/devtools/stateSanitizer';
import { listenerMiddleware } from './middleware/listenerMiddleware'; import { listenerMiddleware } from './middleware/listenerMiddleware';
import { authToastMiddleware } from 'services/api/authToastMiddleware';
const allReducers = { const allReducers = {
canvas: canvasReducer, canvas: canvasReducer,
@ -45,7 +46,6 @@ const allReducers = {
system: systemReducer, system: systemReducer,
config: configReducer, config: configReducer,
ui: uiReducer, ui: uiReducer,
hotkeys: hotkeysReducer,
controlAdapters: controlAdaptersReducer, controlAdapters: controlAdaptersReducer,
dynamicPrompts: dynamicPromptsReducer, dynamicPrompts: dynamicPromptsReducer,
deleteImageModal: deleteImageModalReducer, deleteImageModal: deleteImageModalReducer,
@ -55,6 +55,7 @@ const allReducers = {
sdxl: sdxlReducer, sdxl: sdxlReducer,
queue: queueReducer, queue: queueReducer,
workflow: workflowReducer, workflow: workflowReducer,
hrf: hrfReducer,
[api.reducerPath]: api.reducer, [api.reducerPath]: api.reducer,
}; };
@ -76,6 +77,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
'dynamicPrompts', 'dynamicPrompts',
'lora', 'lora',
'modelmanager', 'modelmanager',
'hrf',
]; ];
// Create a custom idb-keyval store (just needed to customize the name) // Create a custom idb-keyval store (just needed to customize the name)
@ -147,4 +149,6 @@ export type RootState = ReturnType<ReturnType<typeof createStore>['getState']>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>; export type AppThunkDispatch = ThunkDispatch<RootState, any, UnknownAction>;
export type AppDispatch = ReturnType<typeof createStore>['dispatch']; export type AppDispatch = ReturnType<typeof createStore>['dispatch'];
export const stateSelector = (state: RootState) => state; export function stateSelector(state: RootState) {
return state;
}

View File

@ -1,5 +1,6 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import type { AppThunkDispatch, RootState } from 'app/store/store';
import { AppThunkDispatch, RootState } from 'app/store/store'; import type { TypedUseSelectorHook } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
// Use throughout your app instead of plain `useDispatch` and `useSelector` // Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppThunkDispatch>(); export const useAppDispatch = () => useDispatch<AppThunkDispatch>();

View File

@ -1,6 +1,6 @@
import { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants'; import type { CONTROLNET_PROCESSORS } from 'features/controlAdapters/store/constants';
import { InvokeTabName } from 'features/ui/store/tabMap'; import type { InvokeTabName } from 'features/ui/store/tabMap';
import { O } from 'ts-toolbelt'; import type { O } from 'ts-toolbelt';
/** /**
* A disable-able application feature * A disable-able application feature

View File

@ -0,0 +1,64 @@
import { Flex } from '@chakra-ui/layout';
import type { Meta, StoryObj } from '@storybook/react';
import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { useState } from 'react';
import { AspectRatioPreview } from './AspectRatioPreview';
const meta: Meta<typeof AspectRatioPreview> = {
title: 'Components/AspectRatioPreview',
tags: ['autodocs'],
component: AspectRatioPreview,
};
export default meta;
type Story = StoryObj<typeof InvControl>;
const MIN = 64;
const MAX = 1024;
const STEP = 64;
const FINE_STEP = 8;
const INITIAL = 512;
const MARKS = Array.from(
{ length: Math.floor(MAX / STEP) },
(_, i) => MIN + i * STEP
);
const Component = () => {
const [width, setWidth] = useState(INITIAL);
const [height, setHeight] = useState(INITIAL);
return (
<Flex w="full" flexDir="column">
<InvControl label="Width">
<InvSlider
value={width}
min={MIN}
max={MAX}
step={STEP}
fineStep={FINE_STEP}
onChange={setWidth}
marks={MARKS}
/>
</InvControl>
<InvControl label="Height">
<InvSlider
value={height}
min={MIN}
max={MAX}
step={STEP}
fineStep={FINE_STEP}
onChange={setHeight}
marks={MARKS}
/>
</InvControl>
<Flex h={96} w={96} p={4}>
<AspectRatioPreview width={width} height={height} />
</Flex>
</Flex>
);
};
export const AspectRatioWithSliderInvControls: Story = {
render: Component,
};

View File

@ -0,0 +1,60 @@
import { Flex, Icon } from '@chakra-ui/react';
import { useSize } from '@chakra-ui/react-use-size';
import { AnimatePresence, motion } from 'framer-motion';
import { useRef } from 'react';
import { FaImage } from 'react-icons/fa';
import {
BOX_SIZE_CSS_CALC,
ICON_CONTAINER_STYLES,
MOTION_ICON_ANIMATE,
MOTION_ICON_EXIT,
MOTION_ICON_INITIAL,
} from './constants';
import { useAspectRatioPreviewState } from './hooks';
import type { AspectRatioPreviewProps } from './types';
export const AspectRatioPreview = (props: AspectRatioPreviewProps) => {
const { width: _width, height: _height, icon = FaImage } = props;
const containerRef = useRef<HTMLDivElement>(null);
const containerSize = useSize(containerRef);
const { width, height, shouldShowIcon } = useAspectRatioPreviewState({
width: _width,
height: _height,
containerSize,
});
return (
<Flex
w="full"
h="full"
alignItems="center"
justifyContent="center"
ref={containerRef}
>
<Flex
bg="blackAlpha.400"
borderRadius="base"
width={`${width}px`}
height={`${height}px`}
alignItems="center"
justifyContent="center"
>
<AnimatePresence>
{shouldShowIcon && (
<Flex
as={motion.div}
initial={MOTION_ICON_INITIAL}
animate={MOTION_ICON_ANIMATE}
exit={MOTION_ICON_EXIT}
style={ICON_CONTAINER_STYLES}
>
<Icon as={icon} color="base.700" boxSize={BOX_SIZE_CSS_CALC} />
</Flex>
)}
</AnimatePresence>
</Flex>
</Flex>
);
};

View File

@ -0,0 +1,23 @@
// When the aspect ratio is between these two values, we show the icon (experimentally determined)
export const ICON_LOW_CUTOFF = 0.23;
export const ICON_HIGH_CUTOFF = 1 / ICON_LOW_CUTOFF;
export const ICON_SIZE_PX = 48;
export const ICON_PADDING_PX = 16;
export const BOX_SIZE_CSS_CALC = `min(${ICON_SIZE_PX}px, calc(100% - ${ICON_PADDING_PX}px))`;
export const MOTION_ICON_INITIAL = {
opacity: 0,
};
export const MOTION_ICON_ANIMATE = {
opacity: 1,
transition: { duration: 0.1 },
};
export const MOTION_ICON_EXIT = {
opacity: 0,
transition: { duration: 0.1 },
};
export const ICON_CONTAINER_STYLES = {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
};

View File

@ -0,0 +1,48 @@
import { useMemo } from 'react';
import { ICON_HIGH_CUTOFF, ICON_LOW_CUTOFF } from './constants';
type Dimensions = {
width: number;
height: number;
};
type UseAspectRatioPreviewStateArg = {
width: number;
height: number;
containerSize?: Dimensions;
};
type UseAspectRatioPreviewState = (
arg: UseAspectRatioPreviewStateArg
) => Dimensions & { shouldShowIcon: boolean };
export const useAspectRatioPreviewState: UseAspectRatioPreviewState = ({
width: _width,
height: _height,
containerSize,
}) => {
const dimensions = useMemo(() => {
if (!containerSize) {
return { width: 0, height: 0, shouldShowIcon: false };
}
const aspectRatio = _width / _height;
let width = _width;
let height = _height;
if (_width > _height) {
width = containerSize.width;
height = width / aspectRatio;
} else {
height = containerSize.height;
width = height * aspectRatio;
}
const shouldShowIcon =
aspectRatio < ICON_HIGH_CUTOFF && aspectRatio > ICON_LOW_CUTOFF;
return { width, height, shouldShowIcon };
}, [_height, _width, containerSize]);
return dimensions;
};

View File

@ -0,0 +1,7 @@
import type { IconType } from 'react-icons';
export type AspectRatioPreviewProps = {
width: number;
height: number;
icon?: IconType;
};

View File

@ -1,22 +0,0 @@
import { Box, Image } from '@chakra-ui/react';
import InvokeAILogoImage from 'assets/images/logo.png';
import { memo } from 'react';
const GreyscaleInvokeAIIcon = () => (
<Box pos="relative" w={4} h={4}>
<Image
src={InvokeAILogoImage}
alt="invoke-ai-logo"
pos="absolute"
top={-0.5}
insetInlineStart={-0.5}
w={5}
h={5}
minW={5}
minH={5}
filter="saturate(0)"
/>
</Box>
);
export default memo(GreyscaleInvokeAIIcon);

View File

@ -1,93 +0,0 @@
import {
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
forwardRef,
useDisclosure,
} from '@chakra-ui/react';
import {
cloneElement,
memo,
ReactElement,
ReactNode,
useCallback,
useRef,
} from 'react';
import { useTranslation } from 'react-i18next';
import IAIButton from './IAIButton';
type Props = {
acceptButtonText?: string;
acceptCallback: () => void;
cancelButtonText?: string;
cancelCallback?: () => void;
children: ReactNode;
title: string;
triggerComponent: ReactElement;
};
const IAIAlertDialog = forwardRef((props: Props, ref) => {
const { t } = useTranslation();
const {
acceptButtonText = t('common.accept'),
acceptCallback,
cancelButtonText = t('common.cancel'),
cancelCallback,
children,
title,
triggerComponent,
} = props;
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef<HTMLButtonElement | null>(null);
const handleAccept = useCallback(() => {
acceptCallback();
onClose();
}, [acceptCallback, onClose]);
const handleCancel = useCallback(() => {
cancelCallback && cancelCallback();
onClose();
}, [cancelCallback, onClose]);
return (
<>
{cloneElement(triggerComponent, {
onClick: onOpen,
ref: ref,
})}
<AlertDialog
isOpen={isOpen}
leastDestructiveRef={cancelRef}
onClose={onClose}
isCentered
>
<AlertDialogOverlay>
<AlertDialogContent>
<AlertDialogHeader fontSize="lg" fontWeight="bold">
{title}
</AlertDialogHeader>
<AlertDialogBody>{children}</AlertDialogBody>
<AlertDialogFooter>
<IAIButton ref={cancelRef} onClick={handleCancel}>
{cancelButtonText}
</IAIButton>
<IAIButton colorScheme="error" onClick={handleAccept} ml={3}>
{acceptButtonText}
</IAIButton>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogOverlay>
</AlertDialog>
</>
);
});
export default memo(IAIAlertDialog);

View File

@ -1,43 +0,0 @@
import {
Button,
ButtonProps,
forwardRef,
Tooltip,
TooltipProps,
} from '@chakra-ui/react';
import { memo, ReactNode } from 'react';
export interface IAIButtonProps extends ButtonProps {
tooltip?: TooltipProps['label'];
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
isChecked?: boolean;
children: ReactNode;
}
const IAIButton = forwardRef((props: IAIButtonProps, forwardedRef) => {
const {
children,
tooltip = '',
tooltipProps: { placement = 'top', hasArrow = true, ...tooltipProps } = {},
isChecked,
...rest
} = props;
return (
<Tooltip
label={tooltip}
placement={placement}
hasArrow={hasArrow}
{...tooltipProps}
>
<Button
ref={forwardedRef}
colorScheme={isChecked ? 'accent' : 'base'}
{...rest}
>
{children}
</Button>
</Tooltip>
);
});
export default memo(IAIButton);

View File

@ -1,106 +0,0 @@
import { ChevronUpIcon } from '@chakra-ui/icons';
import {
Box,
Collapse,
Flex,
Spacer,
Text,
useColorMode,
useDisclosure,
} from '@chakra-ui/react';
import { AnimatePresence, motion } from 'framer-motion';
import { PropsWithChildren, memo } from 'react';
import { mode } from 'theme/util/mode';
export type IAIToggleCollapseProps = PropsWithChildren & {
label: string;
activeLabel?: string;
defaultIsOpen?: boolean;
};
const IAICollapse = (props: IAIToggleCollapseProps) => {
const { label, activeLabel, children, defaultIsOpen = false } = props;
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen });
const { colorMode } = useColorMode();
return (
<Box>
<Flex
onClick={onToggle}
sx={{
alignItems: 'center',
p: 2,
px: 4,
gap: 2,
borderTopRadius: 'base',
borderBottomRadius: isOpen ? 0 : 'base',
bg: mode('base.250', 'base.750')(colorMode),
color: mode('base.900', 'base.100')(colorMode),
_hover: {
bg: mode('base.300', 'base.700')(colorMode),
},
fontSize: 'sm',
fontWeight: 600,
cursor: 'pointer',
transitionProperty: 'common',
transitionDuration: 'normal',
userSelect: 'none',
}}
data-testid={`${label} collapsible`}
>
{label}
<AnimatePresence>
{activeLabel && (
<motion.div
key="statusText"
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
transition: { duration: 0.1 },
}}
exit={{
opacity: 0,
transition: { duration: 0.1 },
}}
>
<Text
sx={{ color: 'accent.500', _dark: { color: 'accent.300' } }}
>
{activeLabel}
</Text>
</motion.div>
)}
</AnimatePresence>
<Spacer />
<ChevronUpIcon
sx={{
w: '1rem',
h: '1rem',
transform: isOpen ? 'rotate(0deg)' : 'rotate(180deg)',
transitionProperty: 'common',
transitionDuration: 'normal',
}}
/>
</Flex>
<Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}>
<Box
sx={{
p: 4,
pb: 4,
borderBottomRadius: 'base',
bg: 'base.150',
_dark: {
bg: 'base.800',
},
}}
>
{children}
</Box>
</Collapse>
</Box>
);
};
export default memo(IAICollapse);

View File

@ -1,8 +1,14 @@
import { ChakraProps, Flex } from '@chakra-ui/react'; import type { ChakraProps } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { RgbaColorPicker } from 'react-colorful'; import { RgbaColorPicker } from 'react-colorful';
import { ColorPickerBaseProps, RgbaColor } from 'react-colorful/dist/types'; import type {
import IAINumberInput from './IAINumberInput'; ColorPickerBaseProps,
RgbaColor,
} from 'react-colorful/dist/types';
import { InvControl } from './InvControl/InvControl';
import { InvNumberInput } from './InvNumberInput/InvNumberInput';
type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & { type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
withNumberInput?: boolean; withNumberInput?: boolean;
@ -52,43 +58,46 @@ const IAIColorPicker = (props: IAIColorPickerProps) => {
/> />
{withNumberInput && ( {withNumberInput && (
<Flex> <Flex>
<IAINumberInput <InvControl label="Red">
<InvNumberInput
value={color.r} value={color.r}
onChange={handleChangeR} onChange={handleChangeR}
min={0} min={0}
max={255} max={255}
step={1} step={1}
label="Red"
w={numberInputWidth} w={numberInputWidth}
/> />
<IAINumberInput </InvControl>
<InvControl label="Green">
<InvNumberInput
value={color.g} value={color.g}
onChange={handleChangeG} onChange={handleChangeG}
min={0} min={0}
max={255} max={255}
step={1} step={1}
label="Green"
w={numberInputWidth} w={numberInputWidth}
/> />
<IAINumberInput </InvControl>
<InvControl label="Blue">
<InvNumberInput
value={color.b} value={color.b}
onChange={handleChangeB} onChange={handleChangeB}
min={0} min={0}
max={255} max={255}
step={1} step={1}
label="Blue"
w={numberInputWidth} w={numberInputWidth}
/> />
<IAINumberInput </InvControl>
<InvControl label="Alpha">
<InvNumberInput
value={color.a} value={color.a}
onChange={handleChangeA} onChange={handleChangeA}
step={0.1} step={0.1}
min={0} min={0}
max={1} max={1}
label="Alpha"
w={numberInputWidth} w={numberInputWidth}
isInteger={false}
/> />
</InvControl>
</Flex> </Flex>
)} )}
</Flex> </Flex>

View File

@ -1,37 +1,29 @@
import { import type { ChakraProps, FlexProps } from '@chakra-ui/react';
ChakraProps, import { Flex, Icon, Image } from '@chakra-ui/react';
Flex,
FlexProps,
Icon,
Image,
useColorMode,
} from '@chakra-ui/react';
import { import {
IAILoadingImageFallback, IAILoadingImageFallback,
IAINoContentFallback, IAINoContentFallback,
} from 'common/components/IAIImageFallback'; } from 'common/components/IAIImageFallback';
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay'; import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton'; import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import type {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'features/dnd/types';
import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu'; import ImageContextMenu from 'features/gallery/components/ImageContextMenu/ImageContextMenu';
import { import type {
MouseEvent, MouseEvent,
ReactElement, ReactElement,
ReactNode, ReactNode,
SyntheticEvent, SyntheticEvent,
memo,
useCallback,
useState,
} from 'react'; } from 'react';
import { memo, useCallback, useState } from 'react';
import { FaImage, FaUpload } from 'react-icons/fa'; import { FaImage, FaUpload } from 'react-icons/fa';
import { ImageDTO, PostUploadAction } from 'services/api/types'; import type { ImageDTO, PostUploadAction } from 'services/api/types';
import { mode } from 'theme/util/mode';
import IAIDraggable from './IAIDraggable'; import IAIDraggable from './IAIDraggable';
import IAIDroppable from './IAIDroppable'; import IAIDroppable from './IAIDroppable';
import SelectionOverlay from './SelectionOverlay'; import SelectionOverlay from './SelectionOverlay';
import {
TypesafeDraggableData,
TypesafeDroppableData,
} from 'features/dnd/types';
const defaultUploadElement = ( const defaultUploadElement = (
<Icon <Icon
@ -98,7 +90,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
dataTestId, dataTestId,
} = props; } = props;
const { colorMode } = useColorMode();
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const handleMouseOver = useCallback( const handleMouseOver = useCallback(
(e: MouseEvent<HTMLDivElement>) => { (e: MouseEvent<HTMLDivElement>) => {
@ -128,10 +119,10 @@ const IAIDndImage = (props: IAIDndImageProps) => {
? {} ? {}
: { : {
cursor: 'pointer', cursor: 'pointer',
bg: mode('base.200', 'base.700')(colorMode), bg: 'base.700',
_hover: { _hover: {
bg: mode('base.300', 'base.650')(colorMode), bg: 'base.650',
color: mode('base.500', 'base.300')(colorMode), color: 'base.300',
}, },
}; };
@ -208,7 +199,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
borderRadius: 'base', borderRadius: 'base',
transitionProperty: 'common', transitionProperty: 'common',
transitionDuration: '0.1s', transitionDuration: '0.1s',
color: mode('base.500', 'base.500')(colorMode), color: 'base.500',
...uploadButtonStyles, ...uploadButtonStyles,
}} }}
{...getUploadButtonProps()} {...getUploadButtonProps()}

View File

@ -1,6 +1,8 @@
import { SystemStyleObject, useColorModeValue } from '@chakra-ui/react'; import type { SystemStyleObject } from '@chakra-ui/react';
import { MouseEvent, ReactElement, memo } from 'react'; import type { MouseEvent, ReactElement } from 'react';
import IAIIconButton from './IAIIconButton'; import { memo } from 'react';
import { InvIconButton } from './InvIconButton/InvIconButton';
type Props = { type Props = {
onClick: (event: MouseEvent<HTMLButtonElement>) => void; onClick: (event: MouseEvent<HTMLButtonElement>) => void;
@ -12,12 +14,8 @@ type Props = {
const IAIDndImageIcon = (props: Props) => { const IAIDndImageIcon = (props: Props) => {
const { onClick, tooltip, icon, styleOverrides } = props; const { onClick, tooltip, icon, styleOverrides } = props;
const resetIconShadow = useColorModeValue(
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-600))`,
`drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))`
);
return ( return (
<IAIIconButton <InvIconButton
onClick={onClick} onClick={onClick}
aria-label={tooltip} aria-label={tooltip}
tooltip={tooltip} tooltip={tooltip}
@ -35,7 +33,7 @@ const IAIDndImageIcon = (props: Props) => {
transitionDuration: 'normal', transitionDuration: 'normal',
fill: 'base.100', fill: 'base.100',
_hover: { fill: 'base.50' }, _hover: { fill: 'base.50' },
filter: resetIconShadow, filter: 'drop-shadow(0px 0px 0.1rem var(--invokeai-colors-base-800))',
}, },
...styleOverrides, ...styleOverrides,
}} }}

View File

@ -1,6 +1,7 @@
import { Box, BoxProps } from '@chakra-ui/react'; import type { BoxProps } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { useDraggableTypesafe } from 'features/dnd/hooks/typesafeHooks'; import { useDraggableTypesafe } from 'features/dnd/hooks/typesafeHooks';
import { TypesafeDraggableData } from 'features/dnd/types'; import type { TypesafeDraggableData } from 'features/dnd/types';
import { memo, useRef } from 'react'; import { memo, useRef } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';

Some files were not shown because too many files have changed in this diff Show More