mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Merge branch 'main' into release/invokeai-3-0-alpha
This commit is contained in:
commit
6c80620c25
@ -5,6 +5,7 @@ from invokeai.app.services.board_record_storage import BoardChanges
|
|||||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
||||||
from invokeai.app.services.models.board_record import BoardDTO
|
from invokeai.app.services.models.board_record import BoardDTO
|
||||||
|
|
||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
boards_router = APIRouter(prefix="/v1/boards", tags=["boards"])
|
boards_router = APIRouter(prefix="/v1/boards", tags=["boards"])
|
||||||
@ -71,10 +72,18 @@ async def update_board(
|
|||||||
@boards_router.delete("/{board_id}", operation_id="delete_board")
|
@boards_router.delete("/{board_id}", operation_id="delete_board")
|
||||||
async def delete_board(
|
async def delete_board(
|
||||||
board_id: str = Path(description="The id of board to delete"),
|
board_id: str = Path(description="The id of board to delete"),
|
||||||
|
include_images: Optional[bool] = Query(
|
||||||
|
description="Permanently delete all images on the board", default=False
|
||||||
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Deletes a board"""
|
"""Deletes a board"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if include_images is True:
|
||||||
|
ApiDependencies.invoker.services.images.delete_images_on_board(
|
||||||
|
board_id=board_id
|
||||||
|
)
|
||||||
|
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
||||||
|
else:
|
||||||
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# TODO: Does this need any exception handling at all?
|
# TODO: Does this need any exception handling at all?
|
||||||
|
@ -18,8 +18,17 @@ config = InvokeAIAppConfig.get_config()
|
|||||||
config.parse_args()
|
config.parse_args()
|
||||||
logger = InvokeAILogger().getLogger(config=config)
|
logger = InvokeAILogger().getLogger(config=config)
|
||||||
|
|
||||||
|
from invokeai.app.services.board_image_record_storage import (
|
||||||
|
SqliteBoardImageRecordStorage,
|
||||||
|
)
|
||||||
|
from invokeai.app.services.board_images import (
|
||||||
|
BoardImagesService,
|
||||||
|
BoardImagesServiceDependencies,
|
||||||
|
)
|
||||||
|
from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage
|
||||||
|
from invokeai.app.services.boards import BoardService, BoardServiceDependencies
|
||||||
from invokeai.app.services.image_record_storage import SqliteImageRecordStorage
|
from invokeai.app.services.image_record_storage import SqliteImageRecordStorage
|
||||||
from invokeai.app.services.images import ImageService
|
from invokeai.app.services.images import ImageService, ImageServiceDependencies
|
||||||
from invokeai.app.services.metadata import CoreMetadataService
|
from invokeai.app.services.metadata import CoreMetadataService
|
||||||
from invokeai.app.services.resource_name import SimpleNameService
|
from invokeai.app.services.resource_name import SimpleNameService
|
||||||
from invokeai.app.services.urls import LocalUrlService
|
from invokeai.app.services.urls import LocalUrlService
|
||||||
@ -230,7 +239,32 @@ def invoke_cli():
|
|||||||
image_file_storage = DiskImageFileStorage(f"{output_folder}/images")
|
image_file_storage = DiskImageFileStorage(f"{output_folder}/images")
|
||||||
names = SimpleNameService()
|
names = SimpleNameService()
|
||||||
|
|
||||||
|
board_record_storage = SqliteBoardRecordStorage(db_location)
|
||||||
|
board_image_record_storage = SqliteBoardImageRecordStorage(db_location)
|
||||||
|
|
||||||
|
boards = BoardService(
|
||||||
|
services=BoardServiceDependencies(
|
||||||
|
board_image_record_storage=board_image_record_storage,
|
||||||
|
board_record_storage=board_record_storage,
|
||||||
|
image_record_storage=image_record_storage,
|
||||||
|
url=urls,
|
||||||
|
logger=logger,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
board_images = BoardImagesService(
|
||||||
|
services=BoardImagesServiceDependencies(
|
||||||
|
board_image_record_storage=board_image_record_storage,
|
||||||
|
board_record_storage=board_record_storage,
|
||||||
|
image_record_storage=image_record_storage,
|
||||||
|
url=urls,
|
||||||
|
logger=logger,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
images = ImageService(
|
images = ImageService(
|
||||||
|
services=ImageServiceDependencies(
|
||||||
|
board_image_record_storage=board_image_record_storage,
|
||||||
image_record_storage=image_record_storage,
|
image_record_storage=image_record_storage,
|
||||||
image_file_storage=image_file_storage,
|
image_file_storage=image_file_storage,
|
||||||
metadata=metadata,
|
metadata=metadata,
|
||||||
@ -239,12 +273,15 @@ def invoke_cli():
|
|||||||
names=names,
|
names=names,
|
||||||
graph_execution_manager=graph_execution_manager,
|
graph_execution_manager=graph_execution_manager,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
services = InvocationServices(
|
services = InvocationServices(
|
||||||
model_manager=model_manager,
|
model_manager=model_manager,
|
||||||
events=events,
|
events=events,
|
||||||
latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f'{output_folder}/latents')),
|
latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f'{output_folder}/latents')),
|
||||||
images=images,
|
images=images,
|
||||||
|
boards=boards,
|
||||||
|
board_images=board_images,
|
||||||
queue=MemoryInvocationQueue(),
|
queue=MemoryInvocationQueue(),
|
||||||
graph_library=SqliteItemStorage[LibraryGraph](
|
graph_library=SqliteItemStorage[LibraryGraph](
|
||||||
filename=db_location, table_name="graphs"
|
filename=db_location, table_name="graphs"
|
||||||
|
@ -85,8 +85,10 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
|||||||
self.__cache_ids = Queue()
|
self.__cache_ids = Queue()
|
||||||
self.__max_cache_size = 10 # TODO: get this from config
|
self.__max_cache_size = 10 # TODO: get this from config
|
||||||
|
|
||||||
self.__output_folder: Path = output_folder if isinstance(output_folder, Path) else Path(output_folder)
|
self.__output_folder: Path = (
|
||||||
self.__thumbnails_folder = self.__output_folder / 'thumbnails'
|
output_folder if isinstance(output_folder, Path) else Path(output_folder)
|
||||||
|
)
|
||||||
|
self.__thumbnails_folder = self.__output_folder / "thumbnails"
|
||||||
|
|
||||||
# Validate required output folders at launch
|
# Validate required output folders at launch
|
||||||
self.__validate_storage_folders()
|
self.__validate_storage_folders()
|
||||||
@ -179,7 +181,9 @@ class DiskImageFileStorage(ImageFileStorageBase):
|
|||||||
def __set_cache(self, image_name: Path, image: PILImageType):
|
def __set_cache(self, image_name: Path, image: PILImageType):
|
||||||
if not image_name in self.__cache:
|
if not image_name in self.__cache:
|
||||||
self.__cache[image_name] = image
|
self.__cache[image_name] = image
|
||||||
self.__cache_ids.put(image_name) # TODO: this should refresh position for LRU cache
|
self.__cache_ids.put(
|
||||||
|
image_name
|
||||||
|
) # TODO: this should refresh position for LRU cache
|
||||||
if len(self.__cache) > self.__max_cache_size:
|
if len(self.__cache) > self.__max_cache_size:
|
||||||
cache_id = self.__cache_ids.get()
|
cache_id = self.__cache_ids.get()
|
||||||
if cache_id in self.__cache:
|
if cache_id in self.__cache:
|
||||||
|
@ -94,6 +94,11 @@ class ImageRecordStorageBase(ABC):
|
|||||||
"""Deletes an image record."""
|
"""Deletes an image record."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete_many(self, image_names: list[str]) -> None:
|
||||||
|
"""Deletes many image records."""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
@ -385,6 +390,25 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
|
|
||||||
|
def delete_many(self, image_names: list[str]) -> None:
|
||||||
|
try:
|
||||||
|
placeholders = ",".join("?" for _ in image_names)
|
||||||
|
|
||||||
|
self._lock.acquire()
|
||||||
|
|
||||||
|
# Construct the SQLite query with the placeholders
|
||||||
|
query = f"DELETE FROM images WHERE image_name IN ({placeholders})"
|
||||||
|
|
||||||
|
# Execute the query with the list of IDs as parameters
|
||||||
|
self._cursor.execute(query, image_names)
|
||||||
|
|
||||||
|
self._conn.commit()
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
self._conn.rollback()
|
||||||
|
raise ImageRecordDeleteException from e
|
||||||
|
finally:
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
def save(
|
def save(
|
||||||
self,
|
self,
|
||||||
image_name: str,
|
image_name: str,
|
||||||
|
@ -112,6 +112,11 @@ class ImageServiceABC(ABC):
|
|||||||
"""Deletes an image."""
|
"""Deletes an image."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete_images_on_board(self, board_id: str):
|
||||||
|
"""Deletes all images on a board."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ImageServiceDependencies:
|
class ImageServiceDependencies:
|
||||||
"""Service dependencies for the ImageService."""
|
"""Service dependencies for the ImageService."""
|
||||||
@ -341,6 +346,28 @@ class ImageService(ImageServiceABC):
|
|||||||
self._services.logger.error("Problem deleting image record and file")
|
self._services.logger.error("Problem deleting image record and file")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
def delete_images_on_board(self, board_id: str):
|
||||||
|
try:
|
||||||
|
images = self._services.board_image_records.get_images_for_board(board_id)
|
||||||
|
image_name_list = list(
|
||||||
|
map(
|
||||||
|
lambda r: r.image_name,
|
||||||
|
images.items,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for image_name in image_name_list:
|
||||||
|
self._services.image_files.delete(image_name)
|
||||||
|
self._services.image_records.delete_many(image_name_list)
|
||||||
|
except ImageRecordDeleteException:
|
||||||
|
self._services.logger.error(f"Failed to delete image records")
|
||||||
|
raise
|
||||||
|
except ImageFileDeleteException:
|
||||||
|
self._services.logger.error(f"Failed to delete image files")
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
self._services.logger.error("Problem deleting image records and files")
|
||||||
|
raise e
|
||||||
|
|
||||||
def _get_metadata(
|
def _get_metadata(
|
||||||
self, session_id: Optional[str] = None, node_id: Optional[str] = None
|
self, session_id: Optional[str] = None, node_id: Optional[str] = None
|
||||||
) -> Union[ImageMetadata, None]:
|
) -> Union[ImageMetadata, None]:
|
||||||
|
@ -326,7 +326,7 @@ class MigrateTo3(object):
|
|||||||
vae_path = p
|
vae_path = p
|
||||||
elif repo_id := vae.get('repo_id'):
|
elif repo_id := vae.get('repo_id'):
|
||||||
if repo_id=='stabilityai/sd-vae-ft-mse': # this guy is already downloaded
|
if repo_id=='stabilityai/sd-vae-ft-mse': # this guy is already downloaded
|
||||||
vae_path = 'models/core/convert/se-vae-ft-mse'
|
vae_path = 'models/core/convert/sd-vae-ft-mse'
|
||||||
else:
|
else:
|
||||||
vae_path = self._download_vae(repo_id, vae.get('subfolder'))
|
vae_path = self._download_vae(repo_id, vae.get('subfolder'))
|
||||||
|
|
||||||
|
@ -137,7 +137,6 @@ def _convert_vae_ckpt_and_cache(
|
|||||||
from .stable_diffusion import _select_ckpt_config
|
from .stable_diffusion import _select_ckpt_config
|
||||||
# all sd models use same vae settings
|
# all sd models use same vae settings
|
||||||
config_file = _select_ckpt_config(base_model, ModelVariantType.Normal)
|
config_file = _select_ckpt_config(base_model, ModelVariantType.Normal)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception(f"Vae conversion not supported for model type: {base_model}")
|
raise Exception(f"Vae conversion not supported for model type: {base_model}")
|
||||||
|
|
||||||
@ -152,7 +151,7 @@ def _convert_vae_ckpt_and_cache(
|
|||||||
if "state_dict" in checkpoint:
|
if "state_dict" in checkpoint:
|
||||||
checkpoint = checkpoint["state_dict"]
|
checkpoint = checkpoint["state_dict"]
|
||||||
|
|
||||||
config = OmegaConf.load(config_file)
|
config = OmegaConf.load(app_config.root_path/config_file)
|
||||||
|
|
||||||
vae_model = convert_ldm_vae_to_diffusers(
|
vae_model = convert_ldm_vae_to_diffusers(
|
||||||
checkpoint = checkpoint,
|
checkpoint = checkpoint,
|
||||||
|
@ -3,12 +3,10 @@ import { visualizer } from 'rollup-plugin-visualizer';
|
|||||||
import { PluginOption, UserConfig } from 'vite';
|
import { 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';
|
||||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
||||||
|
|
||||||
export const commonPlugins: UserConfig['plugins'] = [
|
export const commonPlugins: UserConfig['plugins'] = [
|
||||||
react(),
|
react(),
|
||||||
eslint(),
|
eslint(),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
visualizer() as unknown as PluginOption,
|
visualizer() as unknown as PluginOption,
|
||||||
nodePolyfills(),
|
|
||||||
];
|
];
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.1.0",
|
|
||||||
"@chakra-ui/anatomy": "^2.1.1",
|
"@chakra-ui/anatomy": "^2.1.1",
|
||||||
"@chakra-ui/icons": "^2.0.19",
|
"@chakra-ui/icons": "^2.0.19",
|
||||||
"@chakra-ui/react": "^2.7.1",
|
"@chakra-ui/react": "^2.7.1",
|
||||||
@ -155,7 +154,6 @@
|
|||||||
"vite-plugin-css-injected-by-js": "^3.1.1",
|
"vite-plugin-css-injected-by-js": "^3.1.1",
|
||||||
"vite-plugin-dts": "^2.3.0",
|
"vite-plugin-dts": "^2.3.0",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.9.0",
|
|
||||||
"vite-tsconfig-paths": "^4.2.0",
|
"vite-tsconfig-paths": "^4.2.0",
|
||||||
"yarn": "^1.22.19"
|
"yarn": "^1.22.19"
|
||||||
}
|
}
|
||||||
|
@ -24,16 +24,13 @@
|
|||||||
},
|
},
|
||||||
"common": {
|
"common": {
|
||||||
"hotkeysLabel": "Hotkeys",
|
"hotkeysLabel": "Hotkeys",
|
||||||
"themeLabel": "Theme",
|
"darkMode": "Dark Mode",
|
||||||
|
"lightMode": "Light Mode",
|
||||||
"languagePickerLabel": "Language",
|
"languagePickerLabel": "Language",
|
||||||
"reportBugLabel": "Report Bug",
|
"reportBugLabel": "Report Bug",
|
||||||
"githubLabel": "Github",
|
"githubLabel": "Github",
|
||||||
"discordLabel": "Discord",
|
"discordLabel": "Discord",
|
||||||
"settingsLabel": "Settings",
|
"settingsLabel": "Settings",
|
||||||
"darkTheme": "Dark",
|
|
||||||
"lightTheme": "Light",
|
|
||||||
"greenTheme": "Green",
|
|
||||||
"oceanTheme": "Ocean",
|
|
||||||
"langArabic": "العربية",
|
"langArabic": "العربية",
|
||||||
"langEnglish": "English",
|
"langEnglish": "English",
|
||||||
"langDutch": "Nederlands",
|
"langDutch": "Nederlands",
|
||||||
|
@ -25,6 +25,7 @@ import DeleteImageModal from 'features/gallery/components/DeleteImageModal';
|
|||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
||||||
import { useListModelsQuery } from 'services/api/endpoints/models';
|
import { useListModelsQuery } from 'services/api/endpoints/models';
|
||||||
|
import DeleteBoardImagesModal from '../../features/gallery/components/Boards/DeleteBoardImagesModal';
|
||||||
|
|
||||||
const DEFAULT_CONFIG = {};
|
const DEFAULT_CONFIG = {};
|
||||||
|
|
||||||
@ -158,6 +159,7 @@ const App = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
<DeleteImageModal />
|
<DeleteImageModal />
|
||||||
<UpdateImageBoardModal />
|
<UpdateImageBoardModal />
|
||||||
|
<DeleteBoardImagesModal />
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<GlobalHotkeys />
|
<GlobalHotkeys />
|
||||||
</>
|
</>
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
import UpdateImageBoardModal from '../../features/gallery/components/Boards/UpdateImageBoardModal';
|
||||||
import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext';
|
import { AddImageToBoardContextProvider } from '../contexts/AddImageToBoardContext';
|
||||||
import { $authToken, $baseUrl } from 'services/api/client';
|
import { $authToken, $baseUrl } from 'services/api/client';
|
||||||
|
import { DeleteBoardImagesContextProvider } from '../contexts/DeleteBoardImagesContext';
|
||||||
|
|
||||||
const App = lazy(() => import('./App'));
|
const App = lazy(() => import('./App'));
|
||||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||||
@ -86,11 +87,13 @@ const InvokeAIUI = ({
|
|||||||
<ImageDndContext>
|
<ImageDndContext>
|
||||||
<DeleteImageContextProvider>
|
<DeleteImageContextProvider>
|
||||||
<AddImageToBoardContextProvider>
|
<AddImageToBoardContextProvider>
|
||||||
|
<DeleteBoardImagesContextProvider>
|
||||||
<App
|
<App
|
||||||
config={config}
|
config={config}
|
||||||
headerComponent={headerComponent}
|
headerComponent={headerComponent}
|
||||||
setIsReady={setIsReady}
|
setIsReady={setIsReady}
|
||||||
/>
|
/>
|
||||||
|
</DeleteBoardImagesContextProvider>
|
||||||
</AddImageToBoardContextProvider>
|
</AddImageToBoardContextProvider>
|
||||||
</DeleteImageContextProvider>
|
</DeleteImageContextProvider>
|
||||||
</ImageDndContext>
|
</ImageDndContext>
|
||||||
|
@ -3,17 +3,10 @@ import {
|
|||||||
createLocalStorageManager,
|
createLocalStorageManager,
|
||||||
extendTheme,
|
extendTheme,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { RootState } from 'app/store/store';
|
import { ReactNode, useEffect, useMemo } from 'react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { ReactNode, useEffect } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { theme as invokeAITheme } from 'theme/theme';
|
import { theme as invokeAITheme } from 'theme/theme';
|
||||||
|
|
||||||
import { greenTeaThemeColors } from 'theme/colors/greenTea';
|
|
||||||
import { invokeAIThemeColors } from 'theme/colors/invokeAI';
|
|
||||||
import { lightThemeColors } from 'theme/colors/lightTheme';
|
|
||||||
import { oceanBlueColors } from 'theme/colors/oceanBlue';
|
|
||||||
|
|
||||||
import '@fontsource-variable/inter';
|
import '@fontsource-variable/inter';
|
||||||
import { MantineProvider } from '@mantine/core';
|
import { MantineProvider } from '@mantine/core';
|
||||||
import { mantineTheme } from 'mantine-theme/theme';
|
import { mantineTheme } from 'mantine-theme/theme';
|
||||||
@ -24,29 +17,19 @@ type ThemeLocaleProviderProps = {
|
|||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const THEMES = {
|
|
||||||
dark: invokeAIThemeColors,
|
|
||||||
light: lightThemeColors,
|
|
||||||
green: greenTeaThemeColors,
|
|
||||||
ocean: oceanBlueColors,
|
|
||||||
};
|
|
||||||
|
|
||||||
const manager = createLocalStorageManager('@@invokeai-color-mode');
|
const manager = createLocalStorageManager('@@invokeai-color-mode');
|
||||||
|
|
||||||
function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
|
function ThemeLocaleProvider({ children }: ThemeLocaleProviderProps) {
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
|
|
||||||
const currentTheme = useAppSelector(
|
|
||||||
(state: RootState) => state.ui.currentTheme
|
|
||||||
);
|
|
||||||
|
|
||||||
const direction = i18n.dir();
|
const direction = i18n.dir();
|
||||||
|
|
||||||
const theme = extendTheme({
|
const theme = useMemo(() => {
|
||||||
|
return extendTheme({
|
||||||
...invokeAITheme,
|
...invokeAITheme,
|
||||||
colors: THEMES[currentTheme as keyof typeof THEMES],
|
|
||||||
direction,
|
direction,
|
||||||
});
|
});
|
||||||
|
}, [direction]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.dir = direction;
|
document.body.dir = direction;
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
|
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
||||||
|
import { BoardDTO } from 'services/api/types';
|
||||||
|
import { useDeleteBoardMutation } from '../../services/api/endpoints/boards';
|
||||||
|
import { defaultSelectorOptions } from '../store/util/defaultMemoizeOptions';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { some } from 'lodash-es';
|
||||||
|
import { canvasSelector } from '../../features/canvas/store/canvasSelectors';
|
||||||
|
import { controlNetSelector } from '../../features/controlNet/store/controlNetSlice';
|
||||||
|
import { selectImagesById } from '../../features/gallery/store/imagesSlice';
|
||||||
|
import { nodesSelector } from '../../features/nodes/store/nodesSlice';
|
||||||
|
import { generationSelector } from '../../features/parameters/store/generationSelectors';
|
||||||
|
import { RootState } from '../store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../store/storeHooks';
|
||||||
|
import { ImageUsage } from './DeleteImageContext';
|
||||||
|
import { requestedBoardImagesDeletion } from '../../features/gallery/store/actions';
|
||||||
|
|
||||||
|
export const selectBoardImagesUsage = createSelector(
|
||||||
|
[
|
||||||
|
(state: RootState) => state,
|
||||||
|
generationSelector,
|
||||||
|
canvasSelector,
|
||||||
|
nodesSelector,
|
||||||
|
controlNetSelector,
|
||||||
|
(state: RootState, board_id?: string) => board_id,
|
||||||
|
],
|
||||||
|
(state, generation, canvas, nodes, controlNet, board_id) => {
|
||||||
|
const initialImage = generation.initialImage
|
||||||
|
? selectImagesById(state, generation.initialImage.imageName)
|
||||||
|
: undefined;
|
||||||
|
const isInitialImage = initialImage?.board_id === board_id;
|
||||||
|
|
||||||
|
const isCanvasImage = canvas.layerState.objects.some((obj) => {
|
||||||
|
if (obj.kind === 'image') {
|
||||||
|
const image = selectImagesById(state, obj.imageName);
|
||||||
|
return image?.board_id === board_id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isNodesImage = nodes.nodes.some((node) => {
|
||||||
|
return some(node.data.inputs, (input) => {
|
||||||
|
if (input.type === 'image' && input.value) {
|
||||||
|
const image = selectImagesById(state, input.value.image_name);
|
||||||
|
return image?.board_id === board_id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const isControlNetImage = some(controlNet.controlNets, (c) => {
|
||||||
|
const controlImage = c.controlImage
|
||||||
|
? selectImagesById(state, c.controlImage)
|
||||||
|
: undefined;
|
||||||
|
const processedControlImage = c.processedControlImage
|
||||||
|
? selectImagesById(state, c.processedControlImage)
|
||||||
|
: undefined;
|
||||||
|
return (
|
||||||
|
controlImage?.board_id === board_id ||
|
||||||
|
processedControlImage?.board_id === board_id
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const imageUsage: ImageUsage = {
|
||||||
|
isInitialImage,
|
||||||
|
isCanvasImage,
|
||||||
|
isNodesImage,
|
||||||
|
isControlNetImage,
|
||||||
|
};
|
||||||
|
|
||||||
|
return imageUsage;
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
type DeleteBoardImagesContextValue = {
|
||||||
|
/**
|
||||||
|
* Whether the move image dialog is open.
|
||||||
|
*/
|
||||||
|
isOpen: boolean;
|
||||||
|
/**
|
||||||
|
* Closes the move image dialog.
|
||||||
|
*/
|
||||||
|
onClose: () => void;
|
||||||
|
imagesUsage?: ImageUsage;
|
||||||
|
board?: BoardDTO;
|
||||||
|
onClickDeleteBoardImages: (board: BoardDTO) => void;
|
||||||
|
handleDeleteBoardImages: (boardId: string) => void;
|
||||||
|
handleDeleteBoardOnly: (boardId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DeleteBoardImagesContext =
|
||||||
|
createContext<DeleteBoardImagesContextValue>({
|
||||||
|
isOpen: false,
|
||||||
|
onClose: () => undefined,
|
||||||
|
onClickDeleteBoardImages: () => undefined,
|
||||||
|
handleDeleteBoardImages: () => undefined,
|
||||||
|
handleDeleteBoardOnly: () => undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
type Props = PropsWithChildren;
|
||||||
|
|
||||||
|
export const DeleteBoardImagesContextProvider = (props: Props) => {
|
||||||
|
const [boardToDelete, setBoardToDelete] = useState<BoardDTO>();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
// Check where the board images to be deleted are used (eg init image, controlnet, etc.)
|
||||||
|
const imagesUsage = useAppSelector((state) =>
|
||||||
|
selectBoardImagesUsage(state, boardToDelete?.board_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const [deleteBoard] = useDeleteBoardMutation();
|
||||||
|
|
||||||
|
// Clean up after deleting or dismissing the modal
|
||||||
|
const closeAndClearBoardToDelete = useCallback(() => {
|
||||||
|
setBoardToDelete(undefined);
|
||||||
|
onClose();
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
|
const onClickDeleteBoardImages = useCallback(
|
||||||
|
(board?: BoardDTO) => {
|
||||||
|
console.log({ board });
|
||||||
|
if (!board) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBoardToDelete(board);
|
||||||
|
onOpen();
|
||||||
|
},
|
||||||
|
[setBoardToDelete, onOpen]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteBoardImages = useCallback(
|
||||||
|
(boardId: string) => {
|
||||||
|
if (boardToDelete) {
|
||||||
|
dispatch(
|
||||||
|
requestedBoardImagesDeletion({ board: boardToDelete, imagesUsage })
|
||||||
|
);
|
||||||
|
closeAndClearBoardToDelete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch, closeAndClearBoardToDelete, boardToDelete, imagesUsage]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDeleteBoardOnly = useCallback(
|
||||||
|
(boardId: string) => {
|
||||||
|
if (boardToDelete) {
|
||||||
|
deleteBoard(boardId);
|
||||||
|
closeAndClearBoardToDelete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[deleteBoard, closeAndClearBoardToDelete, boardToDelete]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DeleteBoardImagesContext.Provider
|
||||||
|
value={{
|
||||||
|
isOpen,
|
||||||
|
board: boardToDelete,
|
||||||
|
onClose: closeAndClearBoardToDelete,
|
||||||
|
onClickDeleteBoardImages,
|
||||||
|
handleDeleteBoardImages,
|
||||||
|
handleDeleteBoardOnly,
|
||||||
|
imagesUsage,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</DeleteBoardImagesContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
@ -83,6 +83,7 @@ import {
|
|||||||
addImageRemovedFromBoardRejectedListener,
|
addImageRemovedFromBoardRejectedListener,
|
||||||
} from './listeners/imageRemovedFromBoard';
|
} from './listeners/imageRemovedFromBoard';
|
||||||
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
||||||
|
import { addRequestedBoardImageDeletionListener } from './listeners/boardImagesDeleted';
|
||||||
|
|
||||||
export const listenerMiddleware = createListenerMiddleware();
|
export const listenerMiddleware = createListenerMiddleware();
|
||||||
|
|
||||||
@ -124,6 +125,7 @@ addRequestedImageDeletionListener();
|
|||||||
addImageDeletedPendingListener();
|
addImageDeletedPendingListener();
|
||||||
addImageDeletedFulfilledListener();
|
addImageDeletedFulfilledListener();
|
||||||
addImageDeletedRejectedListener();
|
addImageDeletedRejectedListener();
|
||||||
|
addRequestedBoardImageDeletionListener();
|
||||||
|
|
||||||
// Image metadata
|
// Image metadata
|
||||||
addImageMetadataReceivedFulfilledListener();
|
addImageMetadataReceivedFulfilledListener();
|
||||||
@ -198,7 +200,7 @@ addControlNetImageProcessedListener();
|
|||||||
addControlNetAutoProcessListener();
|
addControlNetAutoProcessListener();
|
||||||
|
|
||||||
// Update image URLs on connect
|
// Update image URLs on connect
|
||||||
addUpdateImageUrlsOnConnectListener();
|
// addUpdateImageUrlsOnConnectListener();
|
||||||
|
|
||||||
// Boards
|
// Boards
|
||||||
addImageAddedToBoardFulfilledListener();
|
addImageAddedToBoardFulfilledListener();
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
import { requestedBoardImagesDeletion } from 'features/gallery/store/actions';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import {
|
||||||
|
imagesRemoved,
|
||||||
|
selectImagesAll,
|
||||||
|
selectImagesById,
|
||||||
|
} from 'features/gallery/store/imagesSlice';
|
||||||
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||||
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { LIST_TAG, api } from 'services/api';
|
||||||
|
import { boardsApi } from '../../../../../services/api/endpoints/boards';
|
||||||
|
|
||||||
|
export const addRequestedBoardImageDeletionListener = () => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: requestedBoardImagesDeletion,
|
||||||
|
effect: async (action, { dispatch, getState, condition }) => {
|
||||||
|
const { board, imagesUsage } = action.payload;
|
||||||
|
|
||||||
|
const { board_id } = board;
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const selectedImage = state.gallery.selectedImage
|
||||||
|
? selectImagesById(state, state.gallery.selectedImage)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (selectedImage && selectedImage.board_id === board_id) {
|
||||||
|
dispatch(imageSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to reset the features where the board images are in use - none of these work if their image(s) don't exist
|
||||||
|
|
||||||
|
if (imagesUsage.isCanvasImage) {
|
||||||
|
dispatch(resetCanvas());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesUsage.isControlNetImage) {
|
||||||
|
dispatch(controlNetReset());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesUsage.isInitialImage) {
|
||||||
|
dispatch(clearInitialImage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesUsage.isNodesImage) {
|
||||||
|
dispatch(nodeEditorReset());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preemptively remove from gallery
|
||||||
|
const images = selectImagesAll(state).reduce((acc: string[], img) => {
|
||||||
|
if (img.board_id === board_id) {
|
||||||
|
acc.push(img.image_name);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
dispatch(imagesRemoved(images));
|
||||||
|
|
||||||
|
// Delete from server
|
||||||
|
dispatch(boardsApi.endpoints.deleteBoardAndImages.initiate(board_id));
|
||||||
|
const result =
|
||||||
|
boardsApi.endpoints.deleteBoardAndImages.select(board_id)(state);
|
||||||
|
const { isSuccess } = result;
|
||||||
|
|
||||||
|
// Wait for successful deletion, then trigger boards to re-fetch
|
||||||
|
const wasBoardDeleted = await condition(() => !!isSuccess, 30000);
|
||||||
|
|
||||||
|
if (wasBoardDeleted) {
|
||||||
|
dispatch(
|
||||||
|
api.util.invalidateTags([
|
||||||
|
{ type: 'Board', id: board_id },
|
||||||
|
{ type: 'Image', id: LIST_TAG },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -1,6 +1,14 @@
|
|||||||
import { ChevronUpIcon } from '@chakra-ui/icons';
|
import { ChevronUpIcon } from '@chakra-ui/icons';
|
||||||
import { Box, Collapse, Flex, Spacer, Switch } from '@chakra-ui/react';
|
import {
|
||||||
|
Box,
|
||||||
|
Collapse,
|
||||||
|
Flex,
|
||||||
|
Spacer,
|
||||||
|
Switch,
|
||||||
|
useColorMode,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { PropsWithChildren, memo } from 'react';
|
import { PropsWithChildren, memo } from 'react';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
export type IAIToggleCollapseProps = PropsWithChildren & {
|
export type IAIToggleCollapseProps = PropsWithChildren & {
|
||||||
label: string;
|
label: string;
|
||||||
@ -11,6 +19,7 @@ export type IAIToggleCollapseProps = PropsWithChildren & {
|
|||||||
|
|
||||||
const IAICollapse = (props: IAIToggleCollapseProps) => {
|
const IAICollapse = (props: IAIToggleCollapseProps) => {
|
||||||
const { label, isOpen, onToggle, children, withSwitch = false } = props;
|
const { label, isOpen, onToggle, children, withSwitch = false } = props;
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Flex
|
<Flex
|
||||||
@ -21,10 +30,14 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
|
|||||||
px: 4,
|
px: 4,
|
||||||
borderTopRadius: 'base',
|
borderTopRadius: 'base',
|
||||||
borderBottomRadius: isOpen ? 0 : 'base',
|
borderBottomRadius: isOpen ? 0 : 'base',
|
||||||
bg: isOpen ? 'base.750' : 'base.800',
|
bg: isOpen
|
||||||
color: 'base.100',
|
? mode('base.200', 'base.750')(colorMode)
|
||||||
|
: mode('base.150', 'base.800')(colorMode),
|
||||||
|
color: mode('base.900', 'base.100')(colorMode),
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: isOpen ? 'base.700' : 'base.750',
|
bg: isOpen
|
||||||
|
? mode('base.250', 'base.700')(colorMode)
|
||||||
|
: mode('base.200', 'base.750')(colorMode),
|
||||||
},
|
},
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@ -50,7 +63,13 @@ const IAICollapse = (props: IAIToggleCollapseProps) => {
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}>
|
<Collapse in={isOpen} animateOpacity style={{ overflow: 'unset' }}>
|
||||||
<Box sx={{ p: 4, borderBottomRadius: 'base', bg: 'base.800' }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 4,
|
||||||
|
borderBottomRadius: 'base',
|
||||||
|
bg: mode('base.100', 'base.800')(colorMode),
|
||||||
|
}}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
Icon,
|
Icon,
|
||||||
IconButtonProps,
|
IconButtonProps,
|
||||||
Image,
|
Image,
|
||||||
|
useColorMode,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
||||||
import { useCombinedRefs } from '@dnd-kit/utilities';
|
import { useCombinedRefs } from '@dnd-kit/utilities';
|
||||||
@ -20,6 +21,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|||||||
import IAIDropOverlay from './IAIDropOverlay';
|
import IAIDropOverlay from './IAIDropOverlay';
|
||||||
import { PostUploadAction } from 'services/api/thunks/image';
|
import { PostUploadAction } from 'services/api/thunks/image';
|
||||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type IAIDndImageProps = {
|
type IAIDndImageProps = {
|
||||||
image: ImageDTO | null | undefined;
|
image: ImageDTO | null | undefined;
|
||||||
@ -62,6 +64,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const dndId = useRef(uuidv4());
|
const dndId = useRef(uuidv4());
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isOver,
|
isOver,
|
||||||
@ -99,10 +102,10 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
bg: 'base.800',
|
bg: mode('base.200', 'base.800')(colorMode),
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'base.750',
|
bg: mode('base.300', 'base.650')(colorMode),
|
||||||
color: 'base.300',
|
color: mode('base.500', 'base.300')(colorMode),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -181,7 +184,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
transitionDuration: '0.1s',
|
transitionDuration: '0.1s',
|
||||||
color: 'base.500',
|
color: mode('base.500', 'base.500')(colorMode),
|
||||||
...uploadButtonStyles,
|
...uploadButtonStyles,
|
||||||
}}
|
}}
|
||||||
{...getUploadButtonProps()}
|
{...getUploadButtonProps()}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Flex, Text } from '@chakra-ui/react';
|
import { Flex, Text, useColorMode } from '@chakra-ui/react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -11,6 +12,7 @@ type Props = {
|
|||||||
export const IAIDropOverlay = (props: Props) => {
|
export const IAIDropOverlay = (props: Props) => {
|
||||||
const { isOver, label = 'Drop' } = props;
|
const { isOver, label = 'Drop' } = props;
|
||||||
const motionId = useRef(uuidv4());
|
const motionId = useRef(uuidv4());
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={motionId.current}
|
key={motionId.current}
|
||||||
@ -42,7 +44,7 @@ export const IAIDropOverlay = (props: Props) => {
|
|||||||
insetInlineStart: 0,
|
insetInlineStart: 0,
|
||||||
w: 'full',
|
w: 'full',
|
||||||
h: 'full',
|
h: 'full',
|
||||||
bg: 'base.900',
|
bg: mode('base.700', 'base.900')(colorMode),
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@ -61,7 +63,9 @@ export const IAIDropOverlay = (props: Props) => {
|
|||||||
h: 'full',
|
h: 'full',
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderColor: isOver ? 'base.200' : 'base.500',
|
borderColor: isOver
|
||||||
|
? mode('base.50', 'base.200')(colorMode)
|
||||||
|
: mode('base.100', 'base.500')(colorMode),
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
borderStyle: 'dashed',
|
borderStyle: 'dashed',
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
@ -75,7 +79,9 @@ export const IAIDropOverlay = (props: Props) => {
|
|||||||
fontSize: '2xl',
|
fontSize: '2xl',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
transform: isOver ? 'scale(1.1)' : 'scale(1)',
|
transform: isOver ? 'scale(1.1)' : 'scale(1)',
|
||||||
color: isOver ? 'base.100' : 'base.500',
|
color: isOver
|
||||||
|
? mode('base.100', 'base.100')(colorMode)
|
||||||
|
: mode('base.200', 'base.500')(colorMode),
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
transitionDuration: '0.1s',
|
transitionDuration: '0.1s',
|
||||||
}}
|
}}
|
||||||
|
@ -6,9 +6,10 @@ import {
|
|||||||
IconProps,
|
IconProps,
|
||||||
Spinner,
|
Spinner,
|
||||||
SpinnerProps,
|
SpinnerProps,
|
||||||
|
useColorMode,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ReactElement } from 'react';
|
|
||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type Props = FlexProps & {
|
type Props = FlexProps & {
|
||||||
spinnerProps?: SpinnerProps;
|
spinnerProps?: SpinnerProps;
|
||||||
@ -17,10 +18,11 @@ type Props = FlexProps & {
|
|||||||
export const IAIImageLoadingFallback = (props: Props) => {
|
export const IAIImageLoadingFallback = (props: Props) => {
|
||||||
const { spinnerProps, ...rest } = props;
|
const { spinnerProps, ...rest } = props;
|
||||||
const { sx, ...restFlexProps } = rest;
|
const { sx, ...restFlexProps } = rest;
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
bg: 'base.900',
|
bg: mode('base.200', 'base.900')(colorMode),
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
w: 'full',
|
w: 'full',
|
||||||
h: 'full',
|
h: 'full',
|
||||||
@ -45,10 +47,12 @@ type IAINoImageFallbackProps = {
|
|||||||
export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
|
export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
|
||||||
const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} };
|
const { sx: flexSx, ...restFlexProps } = props.flexProps ?? { sx: {} };
|
||||||
const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} };
|
const { sx: iconSx, ...restIconProps } = props.iconProps ?? { sx: {} };
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
bg: 'base.900',
|
bg: mode('base.200', 'base.900')(colorMode),
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
w: 'full',
|
w: 'full',
|
||||||
h: 'full',
|
h: 'full',
|
||||||
@ -61,7 +65,7 @@ export const IAINoImageFallback = (props: IAINoImageFallbackProps) => {
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
as={props.as ?? FaImage}
|
as={props.as ?? FaImage}
|
||||||
sx={{ color: 'base.700', ...iconSx }}
|
sx={{ color: mode('base.700', 'base.500')(colorMode), ...iconSx }}
|
||||||
{...restIconProps}
|
{...restIconProps}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Tooltip } from '@chakra-ui/react';
|
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
|
||||||
import { MultiSelect, MultiSelectProps } from '@mantine/core';
|
import { MultiSelect, MultiSelectProps } from '@mantine/core';
|
||||||
|
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type IAIMultiSelectProps = MultiSelectProps & {
|
type IAIMultiSelectProps = MultiSelectProps & {
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
@ -8,71 +10,100 @@ type IAIMultiSelectProps = MultiSelectProps & {
|
|||||||
|
|
||||||
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
||||||
const { searchable = true, tooltip, ...rest } = props;
|
const { searchable = true, tooltip, ...rest } = props;
|
||||||
|
const {
|
||||||
|
base50,
|
||||||
|
base100,
|
||||||
|
base200,
|
||||||
|
base300,
|
||||||
|
base400,
|
||||||
|
base500,
|
||||||
|
base600,
|
||||||
|
base700,
|
||||||
|
base800,
|
||||||
|
base900,
|
||||||
|
accent200,
|
||||||
|
accent300,
|
||||||
|
accent400,
|
||||||
|
accent500,
|
||||||
|
accent600,
|
||||||
|
} = useChakraThemeTokens();
|
||||||
|
const [boxShadow] = useToken('shadows', ['dark-lg']);
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow>
|
<Tooltip label={tooltip} placement="top" hasArrow>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
searchable={searchable}
|
searchable={searchable}
|
||||||
styles={() => ({
|
styles={() => ({
|
||||||
label: {
|
label: {
|
||||||
color: 'var(--invokeai-colors-base-300)',
|
color: mode(base700, base300)(colorMode),
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
},
|
},
|
||||||
searchInput: {
|
searchInput: {
|
||||||
'::placeholder': {
|
':placeholder': {
|
||||||
color: 'var(--invokeai-colors-base-700)',
|
color: mode(base300, base700)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-900)',
|
backgroundColor: mode(base50, base900)(colorMode),
|
||||||
borderWidth: '2px',
|
borderWidth: '2px',
|
||||||
borderColor: 'var(--invokeai-colors-base-800)',
|
borderColor: mode(base200, base800)(colorMode),
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
paddingRight: 24,
|
paddingRight: 24,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
'&:hover': { borderColor: 'var(--invokeai-colors-base-700)' },
|
'&:hover': { borderColor: mode(base300, base600)(colorMode) },
|
||||||
'&:focus': {
|
'&:focus': {
|
||||||
borderColor: 'var(--invokeai-colors-accent-600)',
|
borderColor: mode(accent300, accent600)(colorMode),
|
||||||
|
},
|
||||||
|
'&:is(:focus, :hover)': {
|
||||||
|
borderColor: mode(base400, base500)(colorMode),
|
||||||
},
|
},
|
||||||
'&:focus-within': {
|
'&:focus-within': {
|
||||||
borderColor: 'var(--invokeai-colors-accent-600)',
|
borderColor: mode(accent200, accent600)(colorMode),
|
||||||
|
},
|
||||||
|
'&:disabled': {
|
||||||
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
|
color: mode(base600, base400)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: {
|
value: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-800)',
|
backgroundColor: mode(base200, base800)(colorMode),
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
button: {
|
button: {
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
},
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-700)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dropdown: {
|
dropdown: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-800)',
|
backgroundColor: mode(base200, base800)(colorMode),
|
||||||
borderColor: 'var(--invokeai-colors-base-700)',
|
borderColor: mode(base200, base800)(colorMode),
|
||||||
|
boxShadow,
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-800)',
|
backgroundColor: mode(base200, base800)(colorMode),
|
||||||
color: 'var(--invokeai-colors-base-200)',
|
color: mode(base800, base200)(colorMode),
|
||||||
padding: 6,
|
padding: 6,
|
||||||
'&[data-hovered]': {
|
'&[data-hovered]': {
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
backgroundColor: 'var(--invokeai-colors-base-750)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
},
|
},
|
||||||
'&[data-active]': {
|
'&[data-active]': {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-750)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
backgroundColor: 'var(--invokeai-colors-base-750)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'&[data-selected]': {
|
'&[data-selected]': {
|
||||||
color: 'var(--invokeai-colors-base-50)',
|
backgroundColor: mode(accent400, accent600)(colorMode),
|
||||||
backgroundColor: 'var(--invokeai-colors-accent-650)',
|
color: mode(base50, base100)(colorMode),
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'var(--invokeai-colors-accent-600)',
|
backgroundColor: mode(accent500, accent500)(colorMode),
|
||||||
|
color: mode('white', base50)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -80,7 +111,7 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
|||||||
width: 24,
|
width: 24,
|
||||||
padding: 20,
|
padding: 20,
|
||||||
button: {
|
button: {
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { Tooltip } from '@chakra-ui/react';
|
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
|
||||||
import { Select, SelectProps } from '@mantine/core';
|
import { Select, SelectProps } from '@mantine/core';
|
||||||
|
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
export type IAISelectDataType = {
|
export type IAISelectDataType = {
|
||||||
value: string;
|
value: string;
|
||||||
@ -14,61 +16,105 @@ type IAISelectProps = SelectProps & {
|
|||||||
|
|
||||||
const IAIMantineSelect = (props: IAISelectProps) => {
|
const IAIMantineSelect = (props: IAISelectProps) => {
|
||||||
const { searchable = true, tooltip, ...rest } = props;
|
const { searchable = true, tooltip, ...rest } = props;
|
||||||
|
const {
|
||||||
|
base50,
|
||||||
|
base100,
|
||||||
|
base200,
|
||||||
|
base300,
|
||||||
|
base400,
|
||||||
|
base500,
|
||||||
|
base600,
|
||||||
|
base700,
|
||||||
|
base800,
|
||||||
|
base900,
|
||||||
|
accent200,
|
||||||
|
accent300,
|
||||||
|
accent400,
|
||||||
|
accent500,
|
||||||
|
accent600,
|
||||||
|
} = useChakraThemeTokens();
|
||||||
|
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
|
const [boxShadow] = useToken('shadows', ['dark-lg']);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow>
|
<Tooltip label={tooltip} placement="top" hasArrow>
|
||||||
<Select
|
<Select
|
||||||
searchable={searchable}
|
searchable={searchable}
|
||||||
styles={() => ({
|
styles={() => ({
|
||||||
label: {
|
label: {
|
||||||
color: 'var(--invokeai-colors-base-300)',
|
color: mode(base700, base300)(colorMode),
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-900)',
|
backgroundColor: mode(base50, base900)(colorMode),
|
||||||
borderWidth: '2px',
|
borderWidth: '2px',
|
||||||
borderColor: 'var(--invokeai-colors-base-800)',
|
borderColor: mode(base200, base800)(colorMode),
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
paddingRight: 24,
|
paddingRight: 24,
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
'&:hover': { borderColor: 'var(--invokeai-colors-base-700)' },
|
'&:hover': { borderColor: mode(base300, base600)(colorMode) },
|
||||||
'&:focus': {
|
'&:focus': {
|
||||||
borderColor: 'var(--invokeai-colors-accent-600)',
|
borderColor: mode(accent300, accent600)(colorMode),
|
||||||
|
},
|
||||||
|
'&:is(:focus, :hover)': {
|
||||||
|
borderColor: mode(base400, base500)(colorMode),
|
||||||
|
},
|
||||||
|
'&:focus-within': {
|
||||||
|
borderColor: mode(accent200, accent600)(colorMode),
|
||||||
},
|
},
|
||||||
'&:disabled': {
|
'&:disabled': {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-700)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
color: 'var(--invokeai-colors-base-400)',
|
color: mode(base600, base400)(colorMode),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
backgroundColor: mode(base100, base900)(colorMode),
|
||||||
|
color: mode(base900, base100)(colorMode),
|
||||||
|
button: {
|
||||||
|
color: mode(base900, base100)(colorMode),
|
||||||
|
},
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
|
cursor: 'pointer',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dropdown: {
|
dropdown: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-800)',
|
backgroundColor: mode(base200, base800)(colorMode),
|
||||||
borderColor: 'var(--invokeai-colors-base-700)',
|
borderColor: mode(base200, base800)(colorMode),
|
||||||
|
boxShadow,
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-800)',
|
backgroundColor: mode(base200, base800)(colorMode),
|
||||||
color: 'var(--invokeai-colors-base-200)',
|
color: mode(base800, base200)(colorMode),
|
||||||
padding: 6,
|
padding: 6,
|
||||||
'&[data-hovered]': {
|
'&[data-hovered]': {
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
backgroundColor: 'var(--invokeai-colors-base-750)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
},
|
},
|
||||||
'&[data-active]': {
|
'&[data-active]': {
|
||||||
backgroundColor: 'var(--invokeai-colors-base-750)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
color: 'var(--invokeai-colors-base-100)',
|
color: mode(base900, base100)(colorMode),
|
||||||
backgroundColor: 'var(--invokeai-colors-base-750)',
|
backgroundColor: mode(base300, base700)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'&[data-selected]': {
|
'&[data-selected]': {
|
||||||
color: 'var(--invokeai-colors-base-50)',
|
backgroundColor: mode(accent400, accent600)(colorMode),
|
||||||
backgroundColor: 'var(--invokeai-colors-accent-650)',
|
color: mode(base50, base100)(colorMode),
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'var(--invokeai-colors-accent-600)',
|
backgroundColor: mode(accent500, accent500)(colorMode),
|
||||||
|
color: mode('white', base50)(colorMode),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rightSection: {
|
rightSection: {
|
||||||
width: 32,
|
width: 32,
|
||||||
|
button: {
|
||||||
|
color: mode(base900, base100)(colorMode),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react';
|
import { Checkbox, CheckboxProps, Text, useColorMode } from '@chakra-ui/react';
|
||||||
import { memo, ReactElement } from 'react';
|
import { memo, ReactElement } from 'react';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type IAISimpleCheckboxProps = CheckboxProps & {
|
type IAISimpleCheckboxProps = CheckboxProps & {
|
||||||
label: string | ReactElement;
|
label: string | ReactElement;
|
||||||
@ -7,9 +8,15 @@ type IAISimpleCheckboxProps = CheckboxProps & {
|
|||||||
|
|
||||||
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
|
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
|
||||||
const { label, ...rest } = props;
|
const { label, ...rest } = props;
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
<Checkbox colorScheme="accent" {...rest}>
|
<Checkbox colorScheme="accent" {...rest}>
|
||||||
<Text color="base.200" fontSize="md">
|
<Text
|
||||||
|
sx={{
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: mode('base.800', 'base.200')(colorMode),
|
||||||
|
}}
|
||||||
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
124
invokeai/frontend/web/src/common/hooks/useChakraThemeTokens.ts
Normal file
124
invokeai/frontend/web/src/common/hooks/useChakraThemeTokens.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { useToken } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
export const useChakraThemeTokens = () => {
|
||||||
|
const [
|
||||||
|
base50,
|
||||||
|
base100,
|
||||||
|
base150,
|
||||||
|
base200,
|
||||||
|
base250,
|
||||||
|
base300,
|
||||||
|
base350,
|
||||||
|
base400,
|
||||||
|
base450,
|
||||||
|
base500,
|
||||||
|
base550,
|
||||||
|
base600,
|
||||||
|
base650,
|
||||||
|
base700,
|
||||||
|
base750,
|
||||||
|
base800,
|
||||||
|
base850,
|
||||||
|
base900,
|
||||||
|
base950,
|
||||||
|
accent50,
|
||||||
|
accent100,
|
||||||
|
accent150,
|
||||||
|
accent200,
|
||||||
|
accent250,
|
||||||
|
accent300,
|
||||||
|
accent350,
|
||||||
|
accent400,
|
||||||
|
accent450,
|
||||||
|
accent500,
|
||||||
|
accent550,
|
||||||
|
accent600,
|
||||||
|
accent650,
|
||||||
|
accent700,
|
||||||
|
accent750,
|
||||||
|
accent800,
|
||||||
|
accent850,
|
||||||
|
accent900,
|
||||||
|
accent950,
|
||||||
|
] = useToken('colors', [
|
||||||
|
'base.50',
|
||||||
|
'base.100',
|
||||||
|
'base.150',
|
||||||
|
'base.200',
|
||||||
|
'base.250',
|
||||||
|
'base.300',
|
||||||
|
'base.350',
|
||||||
|
'base.400',
|
||||||
|
'base.450',
|
||||||
|
'base.500',
|
||||||
|
'base.550',
|
||||||
|
'base.600',
|
||||||
|
'base.650',
|
||||||
|
'base.700',
|
||||||
|
'base.750',
|
||||||
|
'base.800',
|
||||||
|
'base.850',
|
||||||
|
'base.900',
|
||||||
|
'base.950',
|
||||||
|
'accent.50',
|
||||||
|
'accent.100',
|
||||||
|
'accent.150',
|
||||||
|
'accent.200',
|
||||||
|
'accent.250',
|
||||||
|
'accent.300',
|
||||||
|
'accent.350',
|
||||||
|
'accent.400',
|
||||||
|
'accent.450',
|
||||||
|
'accent.500',
|
||||||
|
'accent.550',
|
||||||
|
'accent.600',
|
||||||
|
'accent.650',
|
||||||
|
'accent.700',
|
||||||
|
'accent.750',
|
||||||
|
'accent.800',
|
||||||
|
'accent.850',
|
||||||
|
'accent.900',
|
||||||
|
'accent.950',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
base50,
|
||||||
|
base100,
|
||||||
|
base150,
|
||||||
|
base200,
|
||||||
|
base250,
|
||||||
|
base300,
|
||||||
|
base350,
|
||||||
|
base400,
|
||||||
|
base450,
|
||||||
|
base500,
|
||||||
|
base550,
|
||||||
|
base600,
|
||||||
|
base650,
|
||||||
|
base700,
|
||||||
|
base750,
|
||||||
|
base800,
|
||||||
|
base850,
|
||||||
|
base900,
|
||||||
|
base950,
|
||||||
|
accent50,
|
||||||
|
accent100,
|
||||||
|
accent150,
|
||||||
|
accent200,
|
||||||
|
accent250,
|
||||||
|
accent300,
|
||||||
|
accent350,
|
||||||
|
accent400,
|
||||||
|
accent450,
|
||||||
|
accent500,
|
||||||
|
accent550,
|
||||||
|
accent600,
|
||||||
|
accent650,
|
||||||
|
accent700,
|
||||||
|
accent750,
|
||||||
|
accent800,
|
||||||
|
accent850,
|
||||||
|
accent900,
|
||||||
|
accent950,
|
||||||
|
};
|
||||||
|
};
|
@ -1,8 +1,7 @@
|
|||||||
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
|
// Grid drawing adapted from https://longviewcoder.com/2021/12/08/konva-a-better-grid/
|
||||||
|
|
||||||
import { useToken } from '@chakra-ui/react';
|
import { useColorMode, useToken } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from 'app/store/store';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { isEqual, range } from 'lodash-es';
|
import { isEqual, range } from 'lodash-es';
|
||||||
@ -24,14 +23,14 @@ const selector = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const IAICanvasGrid = () => {
|
const IAICanvasGrid = () => {
|
||||||
const currentTheme = useAppSelector(
|
|
||||||
(state: RootState) => state.ui.currentTheme
|
|
||||||
);
|
|
||||||
const { stageScale, stageCoordinates, stageDimensions } =
|
const { stageScale, stageCoordinates, stageDimensions } =
|
||||||
useAppSelector(selector);
|
useAppSelector(selector);
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
const [gridLines, setGridLines] = useState<ReactNode[]>([]);
|
const [gridLines, setGridLines] = useState<ReactNode[]>([]);
|
||||||
|
const [darkGridLineColor, lightGridLineColor] = useToken('colors', [
|
||||||
const [gridLineColor] = useToken('colors', ['gridLineColor']);
|
'base.800',
|
||||||
|
'base.200',
|
||||||
|
]);
|
||||||
|
|
||||||
const unscale = useCallback(
|
const unscale = useCallback(
|
||||||
(value: number) => {
|
(value: number) => {
|
||||||
@ -89,7 +88,7 @@ const IAICanvasGrid = () => {
|
|||||||
x={fullRect.x1 + i * 64}
|
x={fullRect.x1 + i * 64}
|
||||||
y={fullRect.y1}
|
y={fullRect.y1}
|
||||||
points={[0, 0, 0, ySize]}
|
points={[0, 0, 0, ySize]}
|
||||||
stroke={gridLineColor}
|
stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@ -99,7 +98,7 @@ const IAICanvasGrid = () => {
|
|||||||
x={fullRect.x1}
|
x={fullRect.x1}
|
||||||
y={fullRect.y1 + i * 64}
|
y={fullRect.y1 + i * 64}
|
||||||
points={[0, 0, xSize, 0]}
|
points={[0, 0, xSize, 0]}
|
||||||
stroke={gridLineColor}
|
stroke={colorMode === 'dark' ? darkGridLineColor : lightGridLineColor}
|
||||||
strokeWidth={1}
|
strokeWidth={1}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@ -109,9 +108,10 @@ const IAICanvasGrid = () => {
|
|||||||
stageScale,
|
stageScale,
|
||||||
stageCoordinates,
|
stageCoordinates,
|
||||||
stageDimensions,
|
stageDimensions,
|
||||||
currentTheme,
|
|
||||||
unscale,
|
unscale,
|
||||||
gridLineColor,
|
colorMode,
|
||||||
|
darkGridLineColor,
|
||||||
|
lightGridLineColor,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <Group>{gridLines}</Group>;
|
return <Group>{gridLines}</Group>;
|
||||||
|
@ -3,6 +3,7 @@ import { Image, Rect } from 'react-konva';
|
|||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import useImage from 'use-image';
|
import useImage from 'use-image';
|
||||||
import { CanvasImage } from '../store/canvasTypes';
|
import { CanvasImage } from '../store/canvasTypes';
|
||||||
|
import { $authToken } from 'services/api/client';
|
||||||
|
|
||||||
type IAICanvasImageProps = {
|
type IAICanvasImageProps = {
|
||||||
canvasImage: CanvasImage;
|
canvasImage: CanvasImage;
|
||||||
@ -12,7 +13,10 @@ const IAICanvasImage = (props: IAICanvasImageProps) => {
|
|||||||
const { currentData: imageDTO, isError } = useGetImageDTOQuery(
|
const { currentData: imageDTO, isError } = useGetImageDTOQuery(
|
||||||
imageName ?? skipToken
|
imageName ?? skipToken
|
||||||
);
|
);
|
||||||
const [image] = useImage(imageDTO?.image_url ?? '', 'anonymous');
|
const [image] = useImage(
|
||||||
|
imageDTO?.image_url ?? '',
|
||||||
|
$authToken.get() ? 'use-credentials' : 'anonymous'
|
||||||
|
);
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <Rect x={x} y={y} width={width} height={height} fill="red" />;
|
return <Rect x={x} y={y} width={width} height={height} fill="red" />;
|
||||||
|
@ -104,7 +104,10 @@ const IAICanvasStatusText = () => {
|
|||||||
margin: 1,
|
margin: 1,
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
bg: 'base.200',
|
||||||
|
_dark: {
|
||||||
bg: 'base.800',
|
bg: 'base.800',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box, ChakraProps, Flex } from '@chakra-ui/react';
|
import { Box, ChakraProps, Flex, useColorMode } from '@chakra-ui/react';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { FaCopy, FaTrash } from 'react-icons/fa';
|
import { FaCopy, FaTrash } from 'react-icons/fa';
|
||||||
@ -22,6 +22,7 @@ import ParamControlNetShouldAutoConfig from './ParamControlNetShouldAutoConfig';
|
|||||||
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
|
import ParamControlNetBeginEnd from './parameters/ParamControlNetBeginEnd';
|
||||||
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
|
import ParamControlNetControlMode from './parameters/ParamControlNetControlMode';
|
||||||
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
|
import ParamControlNetProcessorSelect from './parameters/ParamControlNetProcessorSelect';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
|
const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
} = props.controlNet;
|
} = props.controlNet;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
dispatch(controlNetRemoved({ controlNetId }));
|
dispatch(controlNetRemoved({ controlNetId }));
|
||||||
}, [controlNetId, dispatch]);
|
}, [controlNetId, dispatch]);
|
||||||
@ -67,7 +68,7 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
gap: 2,
|
gap: 2,
|
||||||
p: 3,
|
p: 3,
|
||||||
bg: 'base.850',
|
bg: mode('base.200', 'base.850')(colorMode),
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
@ -115,7 +116,7 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
sx={{
|
sx={{
|
||||||
boxSize: 4,
|
boxSize: 4,
|
||||||
color: 'base.300',
|
color: mode('base.700', 'base.300')(colorMode),
|
||||||
transform: isExpanded ? 'rotate(0deg)' : 'rotate(180deg)',
|
transform: isExpanded ? 'rotate(0deg)' : 'rotate(180deg)',
|
||||||
transitionProperty: 'common',
|
transitionProperty: 'common',
|
||||||
transitionDuration: 'normal',
|
transitionDuration: 'normal',
|
||||||
@ -130,7 +131,7 @@ const ControlNet = (props: ControlNetProps) => {
|
|||||||
w: 1.5,
|
w: 1.5,
|
||||||
h: 1.5,
|
h: 1.5,
|
||||||
borderRadius: 'full',
|
borderRadius: 'full',
|
||||||
bg: 'error.200',
|
bg: mode('error.700', 'error.200')(colorMode),
|
||||||
top: 4,
|
top: 4,
|
||||||
insetInlineEnd: 4,
|
insetInlineEnd: 4,
|
||||||
}}
|
}}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Flex, Text } from '@chakra-ui/react';
|
import { Flex, Text, useColorMode } from '@chakra-ui/react';
|
||||||
import { FaImages } from 'react-icons/fa';
|
import { FaImages } from 'react-icons/fa';
|
||||||
import { boardIdSelected } from '../../store/boardSlice';
|
import { boardIdSelected } from '../../store/boardSlice';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
@ -10,9 +10,11 @@ import { ImageDTO } from 'services/api/types';
|
|||||||
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
|
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
|
||||||
import { useDroppable } from '@dnd-kit/core';
|
import { useDroppable } from '@dnd-kit/core';
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const handleAllImagesBoardClick = () => {
|
const handleAllImagesBoardClick = () => {
|
||||||
dispatch(boardIdSelected());
|
dispatch(boardIdSelected());
|
||||||
@ -79,7 +81,9 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<Text
|
<Text
|
||||||
sx={{
|
sx={{
|
||||||
color: isSelected ? 'base.50' : 'base.200',
|
color: isSelected
|
||||||
|
? mode('base.900', 'base.50')(colorMode)
|
||||||
|
: mode('base.700', 'base.200')(colorMode),
|
||||||
fontWeight: isSelected ? 600 : undefined,
|
fontWeight: isSelected ? 600 : undefined,
|
||||||
fontSize: 'xs',
|
fontSize: 'xs',
|
||||||
}}
|
}}
|
||||||
|
@ -62,13 +62,13 @@ const BoardsList = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<Collapse in={isOpen} animateOpacity>
|
<Collapse in={isOpen} animateOpacity>
|
||||||
<Flex
|
<Flex
|
||||||
|
layerStyle={'first'}
|
||||||
sx={{
|
sx={{
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
gap: 2,
|
gap: 2,
|
||||||
bg: 'base.800',
|
|
||||||
borderRadius: 'base',
|
|
||||||
p: 2,
|
p: 2,
|
||||||
mt: 2,
|
mt: 2,
|
||||||
|
borderRadius: 'base',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
<Flex sx={{ gap: 2, alignItems: 'center' }}>
|
||||||
|
@ -0,0 +1,114 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogBody,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
ListItem,
|
||||||
|
Text,
|
||||||
|
UnorderedList,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import { memo, useContext, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext';
|
||||||
|
import { some } from 'lodash-es';
|
||||||
|
import { ImageUsage } from '../../../../app/contexts/DeleteImageContext';
|
||||||
|
|
||||||
|
const BoardImageInUseMessage = (props: { imagesUsage?: ImageUsage }) => {
|
||||||
|
const { imagesUsage } = props;
|
||||||
|
|
||||||
|
if (!imagesUsage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!some(imagesUsage)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Text>
|
||||||
|
An image from this board is currently in use in the following features:
|
||||||
|
</Text>
|
||||||
|
<UnorderedList sx={{ paddingInlineStart: 6 }}>
|
||||||
|
{imagesUsage.isInitialImage && <ListItem>Image to Image</ListItem>}
|
||||||
|
{imagesUsage.isCanvasImage && <ListItem>Unified Canvas</ListItem>}
|
||||||
|
{imagesUsage.isControlNetImage && <ListItem>ControlNet</ListItem>}
|
||||||
|
{imagesUsage.isNodesImage && <ListItem>Node Editor</ListItem>}
|
||||||
|
</UnorderedList>
|
||||||
|
<Text>
|
||||||
|
If you delete images from this board, those features will immediately be
|
||||||
|
reset.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DeleteBoardImagesModal = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
board,
|
||||||
|
handleDeleteBoardImages,
|
||||||
|
handleDeleteBoardOnly,
|
||||||
|
imagesUsage,
|
||||||
|
} = useContext(DeleteBoardImagesContext);
|
||||||
|
|
||||||
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog
|
||||||
|
isOpen={isOpen}
|
||||||
|
leastDestructiveRef={cancelRef}
|
||||||
|
onClose={onClose}
|
||||||
|
isCentered
|
||||||
|
>
|
||||||
|
<AlertDialogOverlay>
|
||||||
|
{board && (
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader fontSize="lg" fontWeight="bold">
|
||||||
|
Delete Board
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogBody>
|
||||||
|
<Flex direction="column" gap={3}>
|
||||||
|
<BoardImageInUseMessage imagesUsage={imagesUsage} />
|
||||||
|
<Divider />
|
||||||
|
<Text>{t('common.areYouSure')}</Text>
|
||||||
|
<Text fontWeight="bold">
|
||||||
|
This board has {board.image_count} image(s) that will be
|
||||||
|
deleted.
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</AlertDialogBody>
|
||||||
|
<AlertDialogFooter gap={3}>
|
||||||
|
<IAIButton ref={cancelRef} onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</IAIButton>
|
||||||
|
<IAIButton
|
||||||
|
colorScheme="warning"
|
||||||
|
onClick={() => handleDeleteBoardOnly(board.board_id)}
|
||||||
|
>
|
||||||
|
Delete Board Only
|
||||||
|
</IAIButton>
|
||||||
|
<IAIButton
|
||||||
|
colorScheme="error"
|
||||||
|
onClick={() => handleDeleteBoardImages(board.board_id)}
|
||||||
|
>
|
||||||
|
Delete Board and Images
|
||||||
|
</IAIButton>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
)}
|
||||||
|
</AlertDialogOverlay>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(DeleteBoardImagesModal);
|
@ -8,10 +8,11 @@ import {
|
|||||||
Image,
|
Image,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
|
useColorMode,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback, useContext } from 'react';
|
||||||
import { FaFolder, FaTrash } from 'react-icons/fa';
|
import { FaFolder, FaTrash } from 'react-icons/fa';
|
||||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||||
import { BoardDTO, ImageDTO } from 'services/api/types';
|
import { BoardDTO, ImageDTO } from 'services/api/types';
|
||||||
@ -29,6 +30,8 @@ import { useDroppable } from '@dnd-kit/core';
|
|||||||
import { AnimatePresence } from 'framer-motion';
|
import { AnimatePresence } from 'framer-motion';
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
||||||
import { SelectedItemOverlay } from '../SelectedItemOverlay';
|
import { SelectedItemOverlay } from '../SelectedItemOverlay';
|
||||||
|
import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
interface HoverableBoardProps {
|
interface HoverableBoardProps {
|
||||||
board: BoardDTO;
|
board: BoardDTO;
|
||||||
@ -42,8 +45,12 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
board.cover_image_name ?? skipToken
|
board.cover_image_name ?? skipToken
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const { board_name, board_id } = board;
|
const { board_name, board_id } = board;
|
||||||
|
|
||||||
|
const { onClickDeleteBoardImages } = useContext(DeleteBoardImagesContext);
|
||||||
|
|
||||||
const handleSelectBoard = useCallback(() => {
|
const handleSelectBoard = useCallback(() => {
|
||||||
dispatch(boardIdSelected(board_id));
|
dispatch(boardIdSelected(board_id));
|
||||||
}, [board_id, dispatch]);
|
}, [board_id, dispatch]);
|
||||||
@ -65,6 +72,11 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
deleteBoard(board_id);
|
deleteBoard(board_id);
|
||||||
}, [board_id, deleteBoard]);
|
}, [board_id, deleteBoard]);
|
||||||
|
|
||||||
|
const handleDeleteBoardAndImages = useCallback(() => {
|
||||||
|
console.log({ board });
|
||||||
|
onClickDeleteBoardImages(board);
|
||||||
|
}, [board, onClickDeleteBoardImages]);
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
(droppedImage: ImageDTO) => {
|
(droppedImage: ImageDTO) => {
|
||||||
if (droppedImage.board_id === board_id) {
|
if (droppedImage.board_id === board_id) {
|
||||||
@ -92,9 +104,18 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
menuProps={{ size: 'sm', isLazy: true }}
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
renderMenu={() => (
|
renderMenu={() => (
|
||||||
<MenuList sx={{ visibility: 'visible !important' }}>
|
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||||
|
{board.image_count > 0 && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
sx={{ color: 'error.300' }}
|
sx={{ color: 'error.300' }}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
|
onClickCapture={handleDeleteBoardAndImages}
|
||||||
|
>
|
||||||
|
Delete Board and Images
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem
|
||||||
|
sx={{ color: mode('error.700', 'error.300')(colorMode) }}
|
||||||
|
icon={<FaTrash />}
|
||||||
onClickCapture={handleDeleteBoard}
|
onClickCapture={handleDeleteBoard}
|
||||||
>
|
>
|
||||||
Delete Board
|
Delete Board
|
||||||
@ -163,7 +184,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
>
|
>
|
||||||
<EditablePreview
|
<EditablePreview
|
||||||
sx={{
|
sx={{
|
||||||
color: isSelected ? 'base.50' : 'base.200',
|
color: isSelected
|
||||||
|
? mode('base.900', 'base.50')(colorMode)
|
||||||
|
: mode('base.700', 'base.200')(colorMode),
|
||||||
fontWeight: isSelected ? 600 : undefined,
|
fontWeight: isSelected ? 600 : undefined,
|
||||||
fontSize: 'xs',
|
fontSize: 'xs',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@ -173,9 +196,9 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
/>
|
/>
|
||||||
<EditableInput
|
<EditableInput
|
||||||
sx={{
|
sx={{
|
||||||
color: 'base.50',
|
color: mode('base.900', 'base.50')(colorMode),
|
||||||
fontSize: 'xs',
|
fontSize: 'xs',
|
||||||
borderColor: 'base.500',
|
borderColor: mode('base.500', 'base.500')(colorMode),
|
||||||
p: 0,
|
p: 0,
|
||||||
outline: 0,
|
outline: 0,
|
||||||
}}
|
}}
|
||||||
|
@ -32,7 +32,6 @@ const CurrentImageDisplay = () => {
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
rowGap: 4,
|
rowGap: 4,
|
||||||
borderRadius: 'base',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
}}
|
||||||
|
@ -120,7 +120,7 @@ const GalleryDrawer = () => {
|
|||||||
isResizable={true}
|
isResizable={true}
|
||||||
isOpen={shouldShowGallery}
|
isOpen={shouldShowGallery}
|
||||||
onClose={handleCloseGallery}
|
onClose={handleCloseGallery}
|
||||||
minWidth={200}
|
minWidth={337}
|
||||||
>
|
>
|
||||||
<ImageGalleryContent />
|
<ImageGalleryContent />
|
||||||
</ResizableDrawer>
|
</ResizableDrawer>
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
VStack,
|
VStack,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
useColorMode,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
@ -61,6 +62,7 @@ import BoardsList from './Boards/BoardsList';
|
|||||||
import { boardsSelector } from '../store/boardSlice';
|
import { boardsSelector } from '../store/boardSlice';
|
||||||
import { ChevronUpIcon } from '@chakra-ui/icons';
|
import { ChevronUpIcon } from '@chakra-ui/icons';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
const itemSelector = createSelector(
|
const itemSelector = createSelector(
|
||||||
[(state: RootState) => state],
|
[(state: RootState) => state],
|
||||||
@ -135,6 +137,8 @@ const ImageGalleryContent = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
shouldPinGallery,
|
shouldPinGallery,
|
||||||
galleryImageMinimumWidth,
|
galleryImageMinimumWidth,
|
||||||
@ -267,13 +271,17 @@ const ImageGalleryContent = () => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
px: 2,
|
px: 2,
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'base.800',
|
bg: mode('base.100', 'base.800')(colorMode),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
noOfLines={1}
|
noOfLines={1}
|
||||||
sx={{ w: 'full', color: 'base.200', fontWeight: 600 }}
|
sx={{
|
||||||
|
w: 'full',
|
||||||
|
color: mode('base.800', 'base.200')(colorMode),
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{selectedBoard ? selectedBoard.board_name : 'All Images'}
|
{selectedBoard ? selectedBoard.board_name : 'All Images'}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
|
import { useColorMode, useToken } from '@chakra-ui/react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
export const SelectedItemOverlay = () => (
|
export const SelectedItemOverlay = () => {
|
||||||
|
const [accent400, accent500] = useToken('colors', [
|
||||||
|
'accent.400',
|
||||||
|
'accent.500',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{
|
initial={{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
@ -19,8 +29,12 @@ export const SelectedItemOverlay = () => (
|
|||||||
insetInlineStart: 0,
|
insetInlineStart: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
boxShadow: 'inset 0px 0px 0px 2px var(--invokeai-colors-accent-300)',
|
boxShadow: `inset 0px 0px 0px 2px ${mode(
|
||||||
|
accent400,
|
||||||
|
accent500
|
||||||
|
)(colorMode)}`,
|
||||||
borderRadius: 'var(--invokeai-radii-base)',
|
borderRadius: 'var(--invokeai-radii-base)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { ImageUsage } from 'app/contexts/DeleteImageContext';
|
import { ImageUsage } from 'app/contexts/DeleteImageContext';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO, BoardDTO } from 'services/api/types';
|
||||||
|
|
||||||
export type RequestedImageDeletionArg = {
|
export type RequestedImageDeletionArg = {
|
||||||
image: ImageDTO;
|
image: ImageDTO;
|
||||||
@ -11,6 +11,16 @@ export const requestedImageDeletion = createAction<RequestedImageDeletionArg>(
|
|||||||
'gallery/requestedImageDeletion'
|
'gallery/requestedImageDeletion'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type RequestedBoardImagesDeletionArg = {
|
||||||
|
board: BoardDTO;
|
||||||
|
imagesUsage: ImageUsage;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const requestedBoardImagesDeletion =
|
||||||
|
createAction<RequestedBoardImagesDeletionArg>(
|
||||||
|
'gallery/requestedBoardImagesDeletion'
|
||||||
|
);
|
||||||
|
|
||||||
export const sentImageToCanvas = createAction('gallery/sentImageToCanvas');
|
export const sentImageToCanvas = createAction('gallery/sentImageToCanvas');
|
||||||
|
|
||||||
export const sentImageToImg2Img = createAction('gallery/sentImageToImg2Img');
|
export const sentImageToImg2Img = createAction('gallery/sentImageToImg2Img');
|
||||||
|
@ -60,6 +60,9 @@ const imagesSlice = createSlice({
|
|||||||
imageRemoved: (state, action: PayloadAction<string>) => {
|
imageRemoved: (state, action: PayloadAction<string>) => {
|
||||||
imagesAdapter.removeOne(state, action.payload);
|
imagesAdapter.removeOne(state, action.payload);
|
||||||
},
|
},
|
||||||
|
imagesRemoved: (state, action: PayloadAction<string[]>) => {
|
||||||
|
imagesAdapter.removeMany(state, action.payload);
|
||||||
|
},
|
||||||
imageCategoriesChanged: (state, action: PayloadAction<ImageCategory[]>) => {
|
imageCategoriesChanged: (state, action: PayloadAction<ImageCategory[]>) => {
|
||||||
state.categories = action.payload;
|
state.categories = action.payload;
|
||||||
},
|
},
|
||||||
@ -117,6 +120,7 @@ export const {
|
|||||||
imageUpserted,
|
imageUpserted,
|
||||||
imageUpdatedOne,
|
imageUpdatedOne,
|
||||||
imageRemoved,
|
imageRemoved,
|
||||||
|
imagesRemoved,
|
||||||
imageCategoriesChanged,
|
imageCategoriesChanged,
|
||||||
} = imagesSlice.actions;
|
} = imagesSlice.actions;
|
||||||
|
|
||||||
|
@ -12,15 +12,25 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
|||||||
const { nodeId, template } = props;
|
const { nodeId, template } = props;
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
borderTopRadius="md"
|
sx={{
|
||||||
justifyContent="space-between"
|
borderTopRadius: 'md',
|
||||||
background="base.700"
|
alignItems: 'center',
|
||||||
px={2}
|
justifyContent: 'space-between',
|
||||||
py={1}
|
px: 2,
|
||||||
alignItems="center"
|
py: 1,
|
||||||
|
bg: 'base.300',
|
||||||
|
_dark: { bg: 'base.700' },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip label={nodeId}>
|
<Tooltip label={nodeId}>
|
||||||
<Heading size="xs" fontWeight={600} color="base.100">
|
<Heading
|
||||||
|
size="xs"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
color: 'base.900',
|
||||||
|
_dark: { color: 'base.100' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
{template.title}
|
{template.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -30,7 +40,16 @@ const IAINodeHeader = (props: IAINodeHeaderProps) => {
|
|||||||
hasArrow
|
hasArrow
|
||||||
shouldWrapChildren
|
shouldWrapChildren
|
||||||
>
|
>
|
||||||
<Icon color="base.300" as={FaInfoCircle} h="min-content" />
|
<Icon
|
||||||
|
sx={{
|
||||||
|
h: 'min-content',
|
||||||
|
color: 'base.700',
|
||||||
|
_dark: {
|
||||||
|
color: 'base.300',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
as={FaInfoCircle}
|
||||||
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -72,7 +72,14 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
return (
|
return (
|
||||||
<InvocationComponentWrapper selected={selected}>
|
<InvocationComponentWrapper selected={selected}>
|
||||||
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Icon color="base.400" boxSize={32} as={FaExclamationCircle}></Icon>
|
<Icon
|
||||||
|
as={FaExclamationCircle}
|
||||||
|
sx={{
|
||||||
|
boxSize: 32,
|
||||||
|
color: 'base.600',
|
||||||
|
_dark: { color: 'base.400' },
|
||||||
|
}}
|
||||||
|
></Icon>
|
||||||
<IAINodeResizer />
|
<IAINodeResizer />
|
||||||
</Flex>
|
</Flex>
|
||||||
</InvocationComponentWrapper>
|
</InvocationComponentWrapper>
|
||||||
@ -86,8 +93,9 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
sx={{
|
sx={{
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
borderBottomRadius: 'md',
|
borderBottomRadius: 'md',
|
||||||
bg: 'base.800',
|
|
||||||
py: 2,
|
py: 2,
|
||||||
|
bg: 'base.200',
|
||||||
|
_dark: { bg: 'base.800' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />
|
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />
|
||||||
|
@ -8,12 +8,12 @@ import { memo } from 'react';
|
|||||||
const NodeEditor = () => {
|
const NodeEditor = () => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
layerStyle={'first'}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: 'full',
|
width: 'full',
|
||||||
height: 'full',
|
height: 'full',
|
||||||
borderRadius: 'md',
|
borderRadius: 'base',
|
||||||
bg: 'base.850',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
|
@ -11,17 +11,20 @@ const NodeGraphOverlay = () => {
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
as="pre"
|
as="pre"
|
||||||
fontFamily="monospace"
|
sx={{
|
||||||
position="absolute"
|
fontFamily: 'monospace',
|
||||||
top={2}
|
position: 'absolute',
|
||||||
right={2}
|
top: 2,
|
||||||
opacity={0.7}
|
right: 2,
|
||||||
background="base.800"
|
opacity: 0.7,
|
||||||
p={2}
|
p: 2,
|
||||||
maxHeight={500}
|
maxHeight: 500,
|
||||||
maxWidth={500}
|
maxWidth: 500,
|
||||||
overflowY="scroll"
|
overflowY: 'scroll',
|
||||||
borderRadius="md"
|
borderRadius: 'base',
|
||||||
|
bg: 'base.200',
|
||||||
|
_dark: { bg: 'base.800' },
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{JSON.stringify(graph, null, 2)}
|
{JSON.stringify(graph, null, 2)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
import { RootState } from 'app/store/store';
|
import { useColorModeValue } from '@chakra-ui/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { memo } from 'react';
|
||||||
import { CSSProperties, memo } from 'react';
|
|
||||||
import { MiniMap } from 'reactflow';
|
import { MiniMap } from 'reactflow';
|
||||||
|
|
||||||
const MinimapStyle: CSSProperties = {
|
|
||||||
background: 'var(--invokeai-colors-base-500)',
|
|
||||||
};
|
|
||||||
|
|
||||||
const MinimapPanel = () => {
|
const MinimapPanel = () => {
|
||||||
const currentTheme = useAppSelector(
|
const miniMapStyle = useColorModeValue(
|
||||||
(state: RootState) => state.ui.currentTheme
|
{
|
||||||
|
background: 'var(--invokeai-colors-base-200)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
background: 'var(--invokeai-colors-base-500)',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const nodeColor = useColorModeValue(
|
||||||
|
'var(--invokeai-colors-accent-300)',
|
||||||
|
'var(--invokeai-colors-accent-700)'
|
||||||
|
);
|
||||||
|
|
||||||
|
const maskColor = useColorModeValue(
|
||||||
|
'var(--invokeai-colors-blackAlpha-300)',
|
||||||
|
'var(--invokeai-colors-blackAlpha-600)'
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -18,15 +28,9 @@ const MinimapPanel = () => {
|
|||||||
pannable
|
pannable
|
||||||
zoomable
|
zoomable
|
||||||
nodeBorderRadius={30}
|
nodeBorderRadius={30}
|
||||||
style={MinimapStyle}
|
style={miniMapStyle}
|
||||||
nodeColor={
|
nodeColor={nodeColor}
|
||||||
currentTheme === 'light'
|
maskColor={maskColor}
|
||||||
? 'var(--invokeai-colors-accent-700)'
|
|
||||||
: currentTheme === 'green'
|
|
||||||
? 'var(--invokeai-colors-accent-600)'
|
|
||||||
: 'var(--invokeai-colors-accent-700)'
|
|
||||||
}
|
|
||||||
maskColor="var(--invokeai-colors-base-700)"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -333,7 +333,7 @@ export type TypeHints = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type InvocationSchemaExtra = {
|
export type InvocationSchemaExtra = {
|
||||||
output: OpenAPIV3.SchemaObject; // the output of the invocation
|
output: OpenAPIV3.ReferenceObject; // the output of the invocation
|
||||||
ui?: {
|
ui?: {
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
type_hints?: TypeHints;
|
type_hints?: TypeHints;
|
||||||
|
@ -349,11 +349,21 @@ export const getFieldType = (
|
|||||||
|
|
||||||
if (typeHints && name in typeHints) {
|
if (typeHints && name in typeHints) {
|
||||||
rawFieldType = typeHints[name];
|
rawFieldType = typeHints[name];
|
||||||
} else if (!schemaObject.type && schemaObject.allOf) {
|
} else if (!schemaObject.type) {
|
||||||
// if schemaObject has no type, then it should have one of allOf
|
// if schemaObject has no type, then it should have one of allOf, anyOf, oneOf
|
||||||
rawFieldType =
|
if (schemaObject.allOf) {
|
||||||
(schemaObject.allOf[0] as OpenAPIV3.SchemaObject).title ??
|
rawFieldType = refObjectToFieldType(
|
||||||
'Missing Field Type';
|
schemaObject.allOf![0] as OpenAPIV3.ReferenceObject
|
||||||
|
);
|
||||||
|
} else if (schemaObject.anyOf) {
|
||||||
|
rawFieldType = refObjectToFieldType(
|
||||||
|
schemaObject.anyOf![0] as OpenAPIV3.ReferenceObject
|
||||||
|
);
|
||||||
|
} else if (schemaObject.oneOf) {
|
||||||
|
rawFieldType = refObjectToFieldType(
|
||||||
|
schemaObject.oneOf![0] as OpenAPIV3.ReferenceObject
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if (schemaObject.enum) {
|
} else if (schemaObject.enum) {
|
||||||
rawFieldType = 'enum';
|
rawFieldType = 'enum';
|
||||||
} else if (schemaObject.type) {
|
} else if (schemaObject.type) {
|
||||||
|
@ -5,48 +5,33 @@ import {
|
|||||||
InputFieldTemplate,
|
InputFieldTemplate,
|
||||||
InvocationSchemaObject,
|
InvocationSchemaObject,
|
||||||
InvocationTemplate,
|
InvocationTemplate,
|
||||||
|
isInvocationSchemaObject,
|
||||||
OutputFieldTemplate,
|
OutputFieldTemplate,
|
||||||
} from '../types/types';
|
} from '../types/types';
|
||||||
import { buildInputFieldTemplate, getFieldType } from './fieldTemplateBuilders';
|
import {
|
||||||
import { O } from 'ts-toolbelt';
|
buildInputFieldTemplate,
|
||||||
|
buildOutputFieldTemplates,
|
||||||
// recursively exclude all properties of type U from T
|
} from './fieldTemplateBuilders';
|
||||||
type DeepExclude<T, U> = T extends U
|
|
||||||
? never
|
|
||||||
: T extends object
|
|
||||||
? {
|
|
||||||
[K in keyof T]: DeepExclude<T[K], U>;
|
|
||||||
}
|
|
||||||
: T;
|
|
||||||
|
|
||||||
// The schema from swagger-parser is dereferenced, and we know `components` and `components.schemas` exist
|
|
||||||
type DereferencedOpenAPIDocument = DeepExclude<
|
|
||||||
O.Required<OpenAPIV3.Document, 'schemas' | 'components', 'deep'>,
|
|
||||||
OpenAPIV3.ReferenceObject
|
|
||||||
>;
|
|
||||||
|
|
||||||
const RESERVED_FIELD_NAMES = ['id', 'type', 'is_intermediate'];
|
const RESERVED_FIELD_NAMES = ['id', 'type', 'is_intermediate'];
|
||||||
|
|
||||||
const invocationDenylist = ['Graph', 'InvocationMeta'];
|
const invocationDenylist = ['Graph', 'InvocationMeta'];
|
||||||
|
|
||||||
const nodeFilter = (
|
export const parseSchema = (openAPI: OpenAPIV3.Document) => {
|
||||||
schema: DereferencedOpenAPIDocument['components']['schemas'][string],
|
// filter out non-invocation schemas, plus some tricky invocations for now
|
||||||
key: string
|
const filteredSchemas = filter(
|
||||||
) =>
|
openAPI.components!.schemas,
|
||||||
|
(schema, key) =>
|
||||||
key.includes('Invocation') &&
|
key.includes('Invocation') &&
|
||||||
!key.includes('InvocationOutput') &&
|
!key.includes('InvocationOutput') &&
|
||||||
!invocationDenylist.some((denylistItem) => key.includes(denylistItem));
|
!invocationDenylist.some((denylistItem) => key.includes(denylistItem))
|
||||||
|
) as (OpenAPIV3.ReferenceObject | InvocationSchemaObject)[];
|
||||||
export const parseSchema = (openAPI: DereferencedOpenAPIDocument) => {
|
|
||||||
// filter out non-invocation schemas, plus some tricky invocations for now
|
|
||||||
const filteredSchemas = filter(openAPI.components.schemas, nodeFilter);
|
|
||||||
|
|
||||||
const invocations = filteredSchemas.reduce<
|
const invocations = filteredSchemas.reduce<
|
||||||
Record<string, InvocationTemplate>
|
Record<string, InvocationTemplate>
|
||||||
>((acc, s) => {
|
>((acc, schema) => {
|
||||||
// cast to InvocationSchemaObject, we know the shape
|
// only want SchemaObjects
|
||||||
const schema = s as InvocationSchemaObject;
|
if (isInvocationSchemaObject(schema)) {
|
||||||
|
|
||||||
const type = schema.properties.type.default;
|
const type = schema.properties.type.default;
|
||||||
|
|
||||||
const title = schema.ui?.title ?? schema.title.replace('Invocation', '');
|
const title = schema.ui?.title ?? schema.title.replace('Invocation', '');
|
||||||
@ -56,8 +41,10 @@ export const parseSchema = (openAPI: DereferencedOpenAPIDocument) => {
|
|||||||
const inputs: Record<string, InputFieldTemplate> = {};
|
const inputs: Record<string, InputFieldTemplate> = {};
|
||||||
|
|
||||||
if (type === 'collect') {
|
if (type === 'collect') {
|
||||||
// Special handling for the Collect node
|
const itemProperty = schema.properties[
|
||||||
const itemProperty = schema.properties['item'] as InvocationSchemaObject;
|
'item'
|
||||||
|
] as InvocationSchemaObject;
|
||||||
|
// Handle the special Collect node
|
||||||
inputs.item = {
|
inputs.item = {
|
||||||
type: 'item',
|
type: 'item',
|
||||||
name: 'item',
|
name: 'item',
|
||||||
@ -68,7 +55,6 @@ export const parseSchema = (openAPI: DereferencedOpenAPIDocument) => {
|
|||||||
default: undefined,
|
default: undefined,
|
||||||
};
|
};
|
||||||
} else if (type === 'iterate') {
|
} else if (type === 'iterate') {
|
||||||
// Special handling for the Iterate node
|
|
||||||
const itemProperty = schema.properties[
|
const itemProperty = schema.properties[
|
||||||
'collection'
|
'collection'
|
||||||
] as InvocationSchemaObject;
|
] as InvocationSchemaObject;
|
||||||
@ -105,12 +91,16 @@ export const parseSchema = (openAPI: DereferencedOpenAPIDocument) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawOutput = (schema as InvocationSchemaObject).output;
|
||||||
|
|
||||||
let outputs: Record<string, OutputFieldTemplate>;
|
let outputs: Record<string, OutputFieldTemplate>;
|
||||||
|
|
||||||
|
// some special handling is needed for collect, iterate and range nodes
|
||||||
if (type === 'iterate') {
|
if (type === 'iterate') {
|
||||||
// Special handling for the Iterate node output
|
// this is guaranteed to be a SchemaObject
|
||||||
const iterationOutput =
|
const iterationOutput = openAPI.components!.schemas![
|
||||||
openAPI.components.schemas['IterateInvocationOutput'];
|
'IterateInvocationOutput'
|
||||||
|
] as OpenAPIV3.SchemaObject;
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
item: {
|
item: {
|
||||||
@ -121,25 +111,7 @@ export const parseSchema = (openAPI: DereferencedOpenAPIDocument) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// All other node outputs
|
outputs = buildOutputFieldTemplates(rawOutput, openAPI, typeHints);
|
||||||
outputs = reduce(
|
|
||||||
schema.output.properties as OpenAPIV3.SchemaObject,
|
|
||||||
(outputsAccumulator, property, propertyName) => {
|
|
||||||
if (!['type', 'id'].includes(propertyName)) {
|
|
||||||
const fieldType = getFieldType(property, propertyName, typeHints);
|
|
||||||
|
|
||||||
outputsAccumulator[propertyName] = {
|
|
||||||
name: propertyName,
|
|
||||||
title: property.title ?? '',
|
|
||||||
description: property.description ?? '',
|
|
||||||
type: fieldType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputsAccumulator;
|
|
||||||
},
|
|
||||||
{} as Record<string, OutputFieldTemplate>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const invocation: InvocationTemplate = {
|
const invocation: InvocationTemplate = {
|
||||||
@ -152,6 +124,7 @@ export const parseSchema = (openAPI: DereferencedOpenAPIDocument) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(acc, { [type]: invocation });
|
Object.assign(acc, { [type]: invocation });
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
@ -4,17 +4,17 @@ import InitialImagePreview from './InitialImagePreview';
|
|||||||
const InitialImageDisplay = () => {
|
const InitialImageDisplay = () => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
layerStyle={'first'}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
rowGap: 4,
|
rowGap: 4,
|
||||||
borderRadius: 'base',
|
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
bg: 'base.850',
|
|
||||||
p: 4,
|
p: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -1,33 +1,32 @@
|
|||||||
|
import {
|
||||||
|
ButtonGroup,
|
||||||
|
ButtonProps,
|
||||||
|
ButtonSpinner,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItemOption,
|
||||||
|
MenuList,
|
||||||
|
MenuOptionGroup,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIIconButton, {
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
IAIIconButtonProps,
|
|
||||||
} from 'common/components/IAIIconButton';
|
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import {
|
import {
|
||||||
|
CancelStrategy,
|
||||||
SystemState,
|
SystemState,
|
||||||
cancelScheduled,
|
cancelScheduled,
|
||||||
cancelTypeChanged,
|
cancelTypeChanged,
|
||||||
CancelStrategy,
|
|
||||||
} from 'features/system/store/systemSlice';
|
} from 'features/system/store/systemSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useCallback, memo, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import {
|
|
||||||
ButtonSpinner,
|
|
||||||
ButtonGroup,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuList,
|
|
||||||
MenuOptionGroup,
|
|
||||||
MenuItemOption,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
|
import { MdCancel, MdCancelScheduleSend } from 'react-icons/md';
|
||||||
|
|
||||||
import { sessionCanceled } from 'services/api/thunks/session';
|
|
||||||
import { ChevronDownIcon } from '@chakra-ui/icons';
|
import { ChevronDownIcon } from '@chakra-ui/icons';
|
||||||
|
import { sessionCanceled } from 'services/api/thunks/session';
|
||||||
|
|
||||||
const cancelButtonSelector = createSelector(
|
const cancelButtonSelector = createSelector(
|
||||||
systemSelector,
|
systemSelector,
|
||||||
@ -55,7 +54,7 @@ interface CancelButtonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CancelButton = (
|
const CancelButton = (
|
||||||
props: CancelButtonProps & Omit<IAIIconButtonProps, 'aria-label'>
|
props: CancelButtonProps & Omit<ButtonProps, 'aria-label'>
|
||||||
) => {
|
) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { btnGroupWidth = 'auto', ...rest } = props;
|
const { btnGroupWidth = 'auto', ...rest } = props;
|
||||||
@ -145,6 +144,7 @@ const CancelButton = (
|
|||||||
paddingY={0}
|
paddingY={0}
|
||||||
colorScheme="error"
|
colorScheme="error"
|
||||||
minWidth={5}
|
minWidth={5}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
<MenuList minWidth="240px">
|
<MenuList minWidth="240px">
|
||||||
<MenuOptionGroup
|
<MenuOptionGroup
|
||||||
|
@ -71,7 +71,7 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
w="100%"
|
w="100%"
|
||||||
tooltip={t('parameters.invoke')}
|
tooltip={t('parameters.invoke')}
|
||||||
tooltipProps={{ placement: 'bottom' }}
|
tooltipProps={{ placement: 'top' }}
|
||||||
colorScheme="accent"
|
colorScheme="accent"
|
||||||
id="invoke-button"
|
id="invoke-button"
|
||||||
{...rest}
|
{...rest}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
import { useColorMode } from '@chakra-ui/react';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaMoon, FaSun } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const ColorModeButton = () => {
|
||||||
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIIconButton
|
||||||
|
aria-label={
|
||||||
|
colorMode === 'dark' ? t('common.lightMode') : t('common.darkMode')
|
||||||
|
}
|
||||||
|
tooltip={
|
||||||
|
colorMode === 'dark' ? t('common.lightMode') : t('common.darkMode')
|
||||||
|
}
|
||||||
|
size="sm"
|
||||||
|
icon={
|
||||||
|
colorMode === 'dark' ? (
|
||||||
|
<FaSun fontSize={19} />
|
||||||
|
) : (
|
||||||
|
<FaMoon fontSize={18} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={toggleColorMode}
|
||||||
|
variant="link"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColorModeButton;
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
ChakraProps,
|
|
||||||
Flex,
|
Flex,
|
||||||
Heading,
|
Heading,
|
||||||
Modal,
|
Modal,
|
||||||
@ -39,6 +38,7 @@ import { UIState } from 'features/ui/store/uiTypes';
|
|||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
|
PropsWithChildren,
|
||||||
ReactElement,
|
ReactElement,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -83,14 +83,6 @@ const selector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const modalSectionStyles: ChakraProps['sx'] = {
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: 2,
|
|
||||||
p: 4,
|
|
||||||
bg: 'base.900',
|
|
||||||
borderRadius: 'base',
|
|
||||||
};
|
|
||||||
|
|
||||||
type ConfigOptions = {
|
type ConfigOptions = {
|
||||||
shouldShowDeveloperSettings: boolean;
|
shouldShowDeveloperSettings: boolean;
|
||||||
shouldShowResetWebUiText: boolean;
|
shouldShowResetWebUiText: boolean;
|
||||||
@ -183,12 +175,12 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
isCentered
|
isCentered
|
||||||
>
|
>
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
<ModalContent paddingInlineEnd={4}>
|
<ModalContent>
|
||||||
<ModalHeader>{t('common.settingsLabel')}</ModalHeader>
|
<ModalHeader>{t('common.settingsLabel')}</ModalHeader>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Flex sx={{ gap: 4, flexDirection: 'column' }}>
|
<Flex sx={{ gap: 4, flexDirection: 'column' }}>
|
||||||
<Flex sx={modalSectionStyles}>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.general')}</Heading>
|
<Heading size="sm">{t('settings.general')}</Heading>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label={t('settings.confirmOnDelete')}
|
label={t('settings.confirmOnDelete')}
|
||||||
@ -197,14 +189,14 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
dispatch(setShouldConfirmOnDelete(e.target.checked))
|
dispatch(setShouldConfirmOnDelete(e.target.checked))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</StyledFlex>
|
||||||
|
|
||||||
<Flex sx={modalSectionStyles}>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.generation')}</Heading>
|
<Heading size="sm">{t('settings.generation')}</Heading>
|
||||||
<SettingsSchedulers />
|
<SettingsSchedulers />
|
||||||
</Flex>
|
</StyledFlex>
|
||||||
|
|
||||||
<Flex sx={modalSectionStyles}>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.ui')}</Heading>
|
<Heading size="sm">{t('settings.ui')}</Heading>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label={t('settings.displayHelpIcons')}
|
label={t('settings.displayHelpIcons')}
|
||||||
@ -245,10 +237,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</StyledFlex>
|
||||||
|
|
||||||
{shouldShowDeveloperSettings && (
|
{shouldShowDeveloperSettings && (
|
||||||
<Flex sx={modalSectionStyles}>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.developer')}</Heading>
|
<Heading size="sm">{t('settings.developer')}</Heading>
|
||||||
<IAISwitch
|
<IAISwitch
|
||||||
label={t('settings.shouldLogToConsole')}
|
label={t('settings.shouldLogToConsole')}
|
||||||
@ -269,10 +261,10 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
dispatch(setEnableImageDebugging(e.target.checked))
|
dispatch(setEnableImageDebugging(e.target.checked))
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</StyledFlex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex sx={modalSectionStyles}>
|
<StyledFlex>
|
||||||
<Heading size="sm">{t('settings.resetWebUI')}</Heading>
|
<Heading size="sm">{t('settings.resetWebUI')}</Heading>
|
||||||
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
|
<IAIButton colorScheme="error" onClick={handleClickResetWebUI}>
|
||||||
{t('settings.resetWebUI')}
|
{t('settings.resetWebUI')}
|
||||||
@ -283,7 +275,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
<Text>{t('settings.resetWebUIDesc2')}</Text>
|
<Text>{t('settings.resetWebUIDesc2')}</Text>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</StyledFlex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
@ -319,3 +311,19 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default SettingsModal;
|
export default SettingsModal;
|
||||||
|
|
||||||
|
const StyledFlex = (props: PropsWithChildren) => {
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
layerStyle="second"
|
||||||
|
sx={{
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 2,
|
||||||
|
p: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -12,8 +12,8 @@ import InvokeAILogoComponent from './InvokeAILogoComponent';
|
|||||||
import LanguagePicker from './LanguagePicker';
|
import LanguagePicker from './LanguagePicker';
|
||||||
import ModelManagerModal from './ModelManager/ModelManagerModal';
|
import ModelManagerModal from './ModelManager/ModelManagerModal';
|
||||||
import SettingsModal from './SettingsModal/SettingsModal';
|
import SettingsModal from './SettingsModal/SettingsModal';
|
||||||
import ThemeChanger from './ThemeChanger';
|
|
||||||
import { useFeatureStatus } from '../hooks/useFeatureStatus';
|
import { useFeatureStatus } from '../hooks/useFeatureStatus';
|
||||||
|
import ColorModeButton from './ColorModeButton';
|
||||||
|
|
||||||
const SiteHeader = () => {
|
const SiteHeader = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -63,8 +63,6 @@ const SiteHeader = () => {
|
|||||||
/>
|
/>
|
||||||
</HotkeysModal>
|
</HotkeysModal>
|
||||||
|
|
||||||
<ThemeChanger />
|
|
||||||
|
|
||||||
{isLocalizationEnabled && <LanguagePicker />}
|
{isLocalizationEnabled && <LanguagePicker />}
|
||||||
|
|
||||||
{isBugLinkEnabled && (
|
{isBugLinkEnabled && (
|
||||||
@ -121,6 +119,8 @@ const SiteHeader = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ColorModeButton />
|
||||||
|
|
||||||
<SettingsModal>
|
<SettingsModal>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
aria-label={t('common.settingsLabel')}
|
aria-label={t('common.settingsLabel')}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Flex, Link } from '@chakra-ui/react';
|
import { Flex, Link } from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaCube, FaKeyboard, FaBug, FaGithub, FaDiscord } from 'react-icons/fa';
|
import { FaBug, FaCube, FaDiscord, FaGithub, FaKeyboard } from 'react-icons/fa';
|
||||||
import { MdSettings } from 'react-icons/md';
|
import { MdSettings } from 'react-icons/md';
|
||||||
import HotkeysModal from './HotkeysModal/HotkeysModal';
|
import HotkeysModal from './HotkeysModal/HotkeysModal';
|
||||||
import LanguagePicker from './LanguagePicker';
|
import LanguagePicker from './LanguagePicker';
|
||||||
import ModelManagerModal from './ModelManager/ModelManagerModal';
|
import ModelManagerModal from './ModelManager/ModelManagerModal';
|
||||||
import SettingsModal from './SettingsModal/SettingsModal';
|
import SettingsModal from './SettingsModal/SettingsModal';
|
||||||
import ThemeChanger from './ThemeChanger';
|
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { useFeatureStatus } from '../hooks/useFeatureStatus';
|
import { useFeatureStatus } from '../hooks/useFeatureStatus';
|
||||||
|
|
||||||
@ -53,8 +53,6 @@ const SiteHeaderMenu = () => {
|
|||||||
/>
|
/>
|
||||||
</HotkeysModal>
|
</HotkeysModal>
|
||||||
|
|
||||||
<ThemeChanger />
|
|
||||||
|
|
||||||
{isLocalizationEnabled && <LanguagePicker />}
|
{isLocalizationEnabled && <LanguagePicker />}
|
||||||
|
|
||||||
{isBugLinkEnabled && (
|
{isBugLinkEnabled && (
|
||||||
|
@ -35,6 +35,18 @@ const statusIndicatorSelector = createSelector(
|
|||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const DARK_COLOR_MAP = {
|
||||||
|
ok: 'green.400',
|
||||||
|
working: 'yellow.400',
|
||||||
|
error: 'red.400',
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIGHT_COLOR_MAP = {
|
||||||
|
ok: 'green.600',
|
||||||
|
working: 'yellow.500',
|
||||||
|
error: 'red.500',
|
||||||
|
};
|
||||||
|
|
||||||
const StatusIndicator = () => {
|
const StatusIndicator = () => {
|
||||||
const {
|
const {
|
||||||
isConnected,
|
isConnected,
|
||||||
@ -46,7 +58,7 @@ const StatusIndicator = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
const statusColorScheme = useMemo(() => {
|
const statusString = useMemo(() => {
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
return 'working';
|
return 'working';
|
||||||
}
|
}
|
||||||
@ -90,9 +102,10 @@ const StatusIndicator = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: `${statusColorScheme}.400`,
|
|
||||||
pb: '1px',
|
pb: '1px',
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
|
color: LIGHT_COLOR_MAP[statusString],
|
||||||
|
_dark: { color: DARK_COLOR_MAP[statusString] },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t(statusTranslationKey as ResourceKey)}
|
{t(statusTranslationKey as ResourceKey)}
|
||||||
@ -101,7 +114,14 @@ const StatusIndicator = () => {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<Icon as={FaCircle} boxSize="0.5rem" color={`${statusColorScheme}.400`} />
|
<Icon
|
||||||
|
as={FaCircle}
|
||||||
|
sx={{
|
||||||
|
boxSize: '0.5rem',
|
||||||
|
color: LIGHT_COLOR_MAP[statusString],
|
||||||
|
_dark: { color: DARK_COLOR_MAP[statusString] },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
import {
|
|
||||||
IconButton,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuItemOption,
|
|
||||||
MenuList,
|
|
||||||
MenuOptionGroup,
|
|
||||||
Tooltip,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { RootState } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { setCurrentTheme } from 'features/ui/store/uiSlice';
|
|
||||||
import i18n from 'i18n';
|
|
||||||
import { map } from 'lodash-es';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { FaPalette } from 'react-icons/fa';
|
|
||||||
|
|
||||||
export const THEMES = {
|
|
||||||
dark: i18n.t('common.darkTheme'),
|
|
||||||
light: i18n.t('common.lightTheme'),
|
|
||||||
green: i18n.t('common.greenTheme'),
|
|
||||||
ocean: i18n.t('common.oceanTheme'),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ThemeChanger() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const currentTheme = useAppSelector(
|
|
||||||
(state: RootState) => state.ui.currentTheme
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu closeOnSelect={false}>
|
|
||||||
<Tooltip label={t('common.themeLabel')} hasArrow>
|
|
||||||
<MenuButton
|
|
||||||
as={IconButton}
|
|
||||||
icon={<FaPalette />}
|
|
||||||
variant="link"
|
|
||||||
aria-label={t('common.themeLabel')}
|
|
||||||
fontSize={20}
|
|
||||||
minWidth={8}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<MenuList>
|
|
||||||
<MenuOptionGroup value={currentTheme}>
|
|
||||||
{map(THEMES, (themeName, themeKey: keyof typeof THEMES) => (
|
|
||||||
<MenuItemOption
|
|
||||||
key={themeKey}
|
|
||||||
value={themeKey}
|
|
||||||
onClick={() => dispatch(setCurrentTheme(themeKey))}
|
|
||||||
>
|
|
||||||
{themeName}
|
|
||||||
</MenuItemOption>
|
|
||||||
))}
|
|
||||||
</MenuOptionGroup>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
@ -51,6 +51,7 @@ const FloatingGalleryButton = () => {
|
|||||||
w: 8,
|
w: 8,
|
||||||
borderStartEndRadius: 0,
|
borderStartEndRadius: 0,
|
||||||
borderEndEndRadius: 0,
|
borderEndEndRadius: 0,
|
||||||
|
shadow: '2xl',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MdPhotoLibrary />
|
<MdPhotoLibrary />
|
||||||
|
@ -19,6 +19,7 @@ import { FaSlidersH } from 'react-icons/fa';
|
|||||||
const floatingButtonStyles: ChakraProps['sx'] = {
|
const floatingButtonStyles: ChakraProps['sx'] = {
|
||||||
borderStartStartRadius: 0,
|
borderStartStartRadius: 0,
|
||||||
borderEndStartRadius: 0,
|
borderEndStartRadius: 0,
|
||||||
|
shadow: '2xl',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const floatingParametersPanelButtonSelector = createSelector(
|
export const floatingParametersPanelButtonSelector = createSelector(
|
||||||
|
@ -36,6 +36,7 @@ import { FaFont, FaImage } from 'react-icons/fa';
|
|||||||
import ResizeHandle from './tabs/ResizeHandle';
|
import ResizeHandle from './tabs/ResizeHandle';
|
||||||
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
|
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
|
||||||
import AuxiliaryProgressIndicator from 'app/components/AuxiliaryProgressIndicator';
|
import AuxiliaryProgressIndicator from 'app/components/AuxiliaryProgressIndicator';
|
||||||
|
import { useMinimumPanelSize } from '../hooks/useMinimumPanelSize';
|
||||||
|
|
||||||
export interface InvokeTabInfo {
|
export interface InvokeTabInfo {
|
||||||
id: InvokeTabName;
|
id: InvokeTabName;
|
||||||
@ -78,6 +79,9 @@ const enabledTabsSelector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const MIN_GALLERY_WIDTH = 300;
|
||||||
|
const DEFAULT_GALLERY_PCT = 20;
|
||||||
|
|
||||||
const InvokeTabs = () => {
|
const InvokeTabs = () => {
|
||||||
const activeTab = useAppSelector(activeTabIndexSelector);
|
const activeTab = useAppSelector(activeTabIndexSelector);
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
@ -150,6 +154,9 @@ const InvokeTabs = () => {
|
|||||||
[enabledTabs]
|
[enabledTabs]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { ref: galleryPanelRef, minSizePct: galleryMinSizePct } =
|
||||||
|
useMinimumPanelSize(MIN_GALLERY_WIDTH, DEFAULT_GALLERY_PCT, 'app');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultIndex={activeTab}
|
defaultIndex={activeTab}
|
||||||
@ -175,6 +182,7 @@ const InvokeTabs = () => {
|
|||||||
<AuxiliaryProgressIndicator />
|
<AuxiliaryProgressIndicator />
|
||||||
</TabList>
|
</TabList>
|
||||||
<PanelGroup
|
<PanelGroup
|
||||||
|
id="app"
|
||||||
autoSaveId="app"
|
autoSaveId="app"
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
style={{ height: '100%', width: '100%' }}
|
style={{ height: '100%', width: '100%' }}
|
||||||
@ -188,11 +196,16 @@ const InvokeTabs = () => {
|
|||||||
<>
|
<>
|
||||||
<ResizeHandle />
|
<ResizeHandle />
|
||||||
<Panel
|
<Panel
|
||||||
|
ref={galleryPanelRef}
|
||||||
onResize={handleResizeGallery}
|
onResize={handleResizeGallery}
|
||||||
id="gallery"
|
id="gallery"
|
||||||
order={3}
|
order={3}
|
||||||
defaultSize={10}
|
defaultSize={
|
||||||
minSize={10}
|
galleryMinSizePct > DEFAULT_GALLERY_PCT
|
||||||
|
? galleryMinSizePct
|
||||||
|
: DEFAULT_GALLERY_PCT
|
||||||
|
}
|
||||||
|
minSize={galleryMinSizePct}
|
||||||
maxSize={50}
|
maxSize={50}
|
||||||
>
|
>
|
||||||
<ImageGalleryContent />
|
<ImageGalleryContent />
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
useOutsideClick,
|
useOutsideClick,
|
||||||
useTheme,
|
useTheme,
|
||||||
SlideDirection,
|
SlideDirection,
|
||||||
|
useColorMode,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
Resizable,
|
Resizable,
|
||||||
@ -21,6 +22,7 @@ import {
|
|||||||
getSlideDirection,
|
getSlideDirection,
|
||||||
getStyles,
|
getStyles,
|
||||||
} from './util';
|
} from './util';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type ResizableDrawerProps = ResizableProps & {
|
type ResizableDrawerProps = ResizableProps & {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -64,7 +66,7 @@ const ResizableDrawer = ({
|
|||||||
sx = {},
|
sx = {},
|
||||||
}: ResizableDrawerProps) => {
|
}: ResizableDrawerProps) => {
|
||||||
const langDirection = useTheme().direction as LangDirection;
|
const langDirection = useTheme().direction as LangDirection;
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
const outsideClickRef = useRef<HTMLDivElement>(null);
|
const outsideClickRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const defaultWidth = useMemo(
|
const defaultWidth = useMemo(
|
||||||
@ -160,11 +162,11 @@ const ResizableDrawer = ({
|
|||||||
handleStyles={handleStyles}
|
handleStyles={handleStyles}
|
||||||
{...minMaxDimensions}
|
{...minMaxDimensions}
|
||||||
sx={{
|
sx={{
|
||||||
borderColor: 'base.800',
|
borderColor: mode('base.200', 'base.800')(colorMode),
|
||||||
p: 4,
|
p: 4,
|
||||||
bg: 'base.900',
|
bg: mode('base.100', 'base.900')(colorMode),
|
||||||
height: 'full',
|
height: 'full',
|
||||||
boxShadow: '0 0 4rem 0 rgba(0, 0, 0, 0.8)',
|
shadow: isOpen ? 'dark-lg' : undefined,
|
||||||
...containerStyles,
|
...containerStyles,
|
||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Box, Flex, FlexProps } from '@chakra-ui/react';
|
import { Box, Flex, FlexProps, useColorMode } from '@chakra-ui/react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import { PanelResizeHandle } from 'react-resizable-panels';
|
import { PanelResizeHandle } from 'react-resizable-panels';
|
||||||
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type ResizeHandleProps = FlexProps & {
|
type ResizeHandleProps = FlexProps & {
|
||||||
direction?: 'horizontal' | 'vertical';
|
direction?: 'horizontal' | 'vertical';
|
||||||
@ -8,6 +9,7 @@ type ResizeHandleProps = FlexProps & {
|
|||||||
|
|
||||||
const ResizeHandle = (props: ResizeHandleProps) => {
|
const ResizeHandle = (props: ResizeHandleProps) => {
|
||||||
const { direction = 'horizontal', ...rest } = props;
|
const { direction = 'horizontal', ...rest } = props;
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
if (direction === 'horizontal') {
|
if (direction === 'horizontal') {
|
||||||
return (
|
return (
|
||||||
@ -21,7 +23,13 @@ const ResizeHandle = (props: ResizeHandleProps) => {
|
|||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<Box sx={{ w: 0.5, h: 'calc(100% - 4px)', bg: 'base.850' }} />
|
<Box
|
||||||
|
sx={{
|
||||||
|
w: 0.5,
|
||||||
|
h: 'calc(100% - 4px)',
|
||||||
|
bg: mode('base.100', 'base.850')(colorMode),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PanelResizeHandle>
|
</PanelResizeHandle>
|
||||||
);
|
);
|
||||||
@ -38,7 +46,13 @@ const ResizeHandle = (props: ResizeHandleProps) => {
|
|||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<Box sx={{ w: 'calc(100% - 4px)', h: 0.5, bg: 'base.850' }} />
|
<Box
|
||||||
|
sx={{
|
||||||
|
w: 'calc(100% - 4px)',
|
||||||
|
h: 0.5,
|
||||||
|
bg: mode('base.100', 'base.850')(colorMode),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PanelResizeHandle>
|
</PanelResizeHandle>
|
||||||
);
|
);
|
||||||
|
@ -4,13 +4,13 @@ import CurrentImageDisplay from 'features/gallery/components/CurrentImageDisplay
|
|||||||
const TextToImageTabMain = () => {
|
const TextToImageTabMain = () => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
layerStyle={'first'}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
borderRadius: 'base',
|
|
||||||
bg: 'base.850',
|
|
||||||
p: 4,
|
p: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -67,14 +67,14 @@ const UnifiedCanvasContent = () => {
|
|||||||
if (shouldUseCanvasBetaLayout) {
|
if (shouldUseCanvasBetaLayout) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
layerStyle="first"
|
||||||
ref={setDroppableRef}
|
ref={setDroppableRef}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
sx={{
|
sx={{
|
||||||
w: 'full',
|
w: 'full',
|
||||||
h: 'full',
|
h: 'full',
|
||||||
borderRadius: 'base',
|
|
||||||
bg: 'base.850',
|
|
||||||
p: 4,
|
p: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
@ -110,11 +110,11 @@ const UnifiedCanvasContent = () => {
|
|||||||
ref={setDroppableRef}
|
ref={setDroppableRef}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
sx={{
|
sx={{
|
||||||
|
layerStyle: 'first',
|
||||||
w: 'full',
|
w: 'full',
|
||||||
h: 'full',
|
h: 'full',
|
||||||
borderRadius: 'base',
|
|
||||||
bg: 'base.850',
|
|
||||||
p: 4,
|
p: 4,
|
||||||
|
borderRadius: 'base',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
// adapted from https://github.com/bvaughn/react-resizable-panels/issues/141#issuecomment-1540048714
|
||||||
|
|
||||||
|
import {
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { ImperativePanelHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
|
export const useMinimumPanelSize = (
|
||||||
|
minSizePx: number,
|
||||||
|
defaultSizePct: number,
|
||||||
|
groupId: string,
|
||||||
|
orientation: 'horizontal' | 'vertical' = 'horizontal'
|
||||||
|
): { ref: RefObject<ImperativePanelHandle>; minSizePct: number } => {
|
||||||
|
const ref = useRef<ImperativePanelHandle>(null);
|
||||||
|
const [minSizePct, setMinSizePct] = useState(defaultSizePct);
|
||||||
|
|
||||||
|
const handleWindowResize = useCallback(() => {
|
||||||
|
const size = ref.current?.getSize();
|
||||||
|
|
||||||
|
if (size !== undefined && size < minSizePct) {
|
||||||
|
ref.current?.resize(minSizePct);
|
||||||
|
}
|
||||||
|
}, [minSizePct]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const panelGroup = document.querySelector(
|
||||||
|
`[data-panel-group-id="${groupId}"]`
|
||||||
|
);
|
||||||
|
const resizeHandles = document.querySelectorAll(
|
||||||
|
'[data-panel-resize-handle-id]'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!panelGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
let dim =
|
||||||
|
orientation === 'horizontal'
|
||||||
|
? panelGroup.getBoundingClientRect().width
|
||||||
|
: panelGroup.getBoundingClientRect().height;
|
||||||
|
|
||||||
|
resizeHandles.forEach((resizeHandle) => {
|
||||||
|
dim -=
|
||||||
|
orientation === 'horizontal'
|
||||||
|
? resizeHandle.getBoundingClientRect().width
|
||||||
|
: resizeHandle.getBoundingClientRect().height;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Minimum size in pixels is a percentage of the PanelGroup's width/height
|
||||||
|
setMinSizePct((minSizePx / dim) * 100);
|
||||||
|
});
|
||||||
|
observer.observe(panelGroup);
|
||||||
|
resizeHandles.forEach((resizeHandle) => {
|
||||||
|
observer.observe(resizeHandle);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleWindowResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
window.removeEventListener('resize', handleWindowResize);
|
||||||
|
};
|
||||||
|
}, [groupId, handleWindowResize, minSizePct, minSizePx, orientation]);
|
||||||
|
|
||||||
|
return { ref, minSizePct };
|
||||||
|
};
|
@ -8,7 +8,6 @@ import { SchedulerParam } from 'features/parameters/store/parameterZodSchemas';
|
|||||||
|
|
||||||
export const initialUIState: UIState = {
|
export const initialUIState: UIState = {
|
||||||
activeTab: 0,
|
activeTab: 0,
|
||||||
currentTheme: 'dark',
|
|
||||||
shouldPinParametersPanel: true,
|
shouldPinParametersPanel: true,
|
||||||
shouldShowParametersPanel: true,
|
shouldShowParametersPanel: true,
|
||||||
shouldShowImageDetails: false,
|
shouldShowImageDetails: false,
|
||||||
@ -30,9 +29,6 @@ export const uiSlice = createSlice({
|
|||||||
setActiveTab: (state, action: PayloadAction<number | InvokeTabName>) => {
|
setActiveTab: (state, action: PayloadAction<number | InvokeTabName>) => {
|
||||||
setActiveTabReducer(state, action.payload);
|
setActiveTabReducer(state, action.payload);
|
||||||
},
|
},
|
||||||
setCurrentTheme: (state, action: PayloadAction<string>) => {
|
|
||||||
state.currentTheme = action.payload;
|
|
||||||
},
|
|
||||||
setShouldPinParametersPanel: (state, action: PayloadAction<boolean>) => {
|
setShouldPinParametersPanel: (state, action: PayloadAction<boolean>) => {
|
||||||
state.shouldPinParametersPanel = action.payload;
|
state.shouldPinParametersPanel = action.payload;
|
||||||
state.shouldShowParametersPanel = true;
|
state.shouldShowParametersPanel = true;
|
||||||
@ -110,7 +106,6 @@ export const uiSlice = createSlice({
|
|||||||
|
|
||||||
export const {
|
export const {
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
setCurrentTheme,
|
|
||||||
setShouldPinParametersPanel,
|
setShouldPinParametersPanel,
|
||||||
setShouldShowParametersPanel,
|
setShouldShowParametersPanel,
|
||||||
setShouldShowImageDetails,
|
setShouldShowImageDetails,
|
||||||
|
@ -16,7 +16,6 @@ export type Rect = Coordinates & Dimensions;
|
|||||||
|
|
||||||
export interface UIState {
|
export interface UIState {
|
||||||
activeTab: number;
|
activeTab: number;
|
||||||
currentTheme: string;
|
|
||||||
shouldPinParametersPanel: boolean;
|
shouldPinParametersPanel: boolean;
|
||||||
shouldShowParametersPanel: boolean;
|
shouldShowParametersPanel: boolean;
|
||||||
shouldShowImageDetails: boolean;
|
shouldShowImageDetails: boolean;
|
||||||
|
33
invokeai/frontend/web/src/i18.d.ts
vendored
33
invokeai/frontend/web/src/i18.d.ts
vendored
@ -1,17 +1,20 @@
|
|||||||
import 'i18next';
|
// TODO: Disabled for IDE performance issues with our translation JSON
|
||||||
|
|
||||||
import en from '../public/locales/en.json';
|
// import 'i18next';
|
||||||
|
|
||||||
declare module 'i18next' {
|
// import en from '../public/locales/en.json';
|
||||||
// Extend CustomTypeOptions
|
|
||||||
interface CustomTypeOptions {
|
// declare module 'i18next' {
|
||||||
// Setting Default Namespace As English
|
// // Extend CustomTypeOptions
|
||||||
defaultNS: 'en';
|
// interface CustomTypeOptions {
|
||||||
// Custom Types For Resources
|
// // Setting Default Namespace As English
|
||||||
resources: {
|
// defaultNS: 'en';
|
||||||
en: typeof en;
|
// // Custom Types For Resources
|
||||||
};
|
// resources: {
|
||||||
// Never Return Null
|
// en: typeof en;
|
||||||
returnNull: false;
|
// };
|
||||||
}
|
// // Never Return Null
|
||||||
}
|
// returnNull: false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
export default {};
|
||||||
|
@ -3,6 +3,8 @@ import LanguageDetector from 'i18next-browser-languagedetector';
|
|||||||
import Backend from 'i18next-http-backend';
|
import Backend from 'i18next-http-backend';
|
||||||
import { initReactI18next } from 'react-i18next';
|
import { initReactI18next } from 'react-i18next';
|
||||||
|
|
||||||
|
// TODO: Disabled for IDE performance issues with our translation JSON
|
||||||
|
// @ts-ignore
|
||||||
import translationEN from '../public/locales/en.json';
|
import translationEN from '../public/locales/en.json';
|
||||||
import { LOCALSTORAGE_PREFIX } from 'app/store/constants';
|
import { LOCALSTORAGE_PREFIX } from 'app/store/constants';
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent';
|
|
||||||
export { default as ThemeChanger } from './features/system/components/ThemeChanger';
|
|
||||||
export { default as IAIPopover } from './common/components/IAIPopover';
|
|
||||||
export { default as IAIIconButton } from './common/components/IAIIconButton';
|
|
||||||
export { default as SettingsModal } from './features/system/components/SettingsModal/SettingsModal';
|
|
||||||
export { default as StatusIndicator } from './features/system/components/StatusIndicator';
|
|
||||||
export { default as ModelSelect } from './features/system/components/ModelSelect';
|
|
||||||
export { default as InvokeAIUI } from './app/components/InvokeAIUI';
|
export { default as InvokeAIUI } from './app/components/InvokeAIUI';
|
||||||
export type { PartialAppConfig } from './app/types/invokeai';
|
export type { PartialAppConfig } from './app/types/invokeai';
|
||||||
|
export { default as IAIIconButton } from './common/components/IAIIconButton';
|
||||||
|
export { default as IAIPopover } from './common/components/IAIPopover';
|
||||||
|
export { default as InvokeAiLogoComponent } from './features/system/components/InvokeAILogoComponent';
|
||||||
|
export { default as ModelSelect } from './features/system/components/ModelSelect';
|
||||||
|
export { default as SettingsModal } from './features/system/components/SettingsModal/SettingsModal';
|
||||||
|
export { default as StatusIndicator } from './features/system/components/StatusIndicator';
|
||||||
|
export { default as ColorModeButton } from './features/system/components/ColorModeButton';
|
||||||
|
@ -2,7 +2,7 @@ import { MantineThemeOverride } from '@mantine/core';
|
|||||||
|
|
||||||
export const mantineTheme: MantineThemeOverride = {
|
export const mantineTheme: MantineThemeOverride = {
|
||||||
colorScheme: 'dark',
|
colorScheme: 'dark',
|
||||||
fontFamily: `'InterVariable', sans-serif`,
|
fontFamily: `'Inter Variable', sans-serif`,
|
||||||
components: {
|
components: {
|
||||||
ScrollArea: {
|
ScrollArea: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
|
@ -82,11 +82,14 @@ export const boardsApi = api.injectEndpoints({
|
|||||||
{ type: 'Board', id: arg.board_id },
|
{ type: 'Board', id: arg.board_id },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
deleteBoard: build.mutation<void, string>({
|
deleteBoard: build.mutation<void, string>({
|
||||||
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }),
|
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }),
|
||||||
invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }],
|
invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }],
|
||||||
}),
|
}),
|
||||||
|
deleteBoardAndImages: build.mutation<void, string>({
|
||||||
|
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE', params: { include_images: true } }),
|
||||||
|
invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }, { type: 'Image', id: LIST_TAG }],
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -96,4 +99,5 @@ export const {
|
|||||||
useCreateBoardMutation,
|
useCreateBoardMutation,
|
||||||
useUpdateBoardMutation,
|
useUpdateBoardMutation,
|
||||||
useDeleteBoardMutation,
|
useDeleteBoardMutation,
|
||||||
|
useDeleteBoardAndImagesMutation
|
||||||
} = boardsApi;
|
} = boardsApi;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
|
||||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
|
||||||
|
|
||||||
const schemaLog = log.child({ namespace: 'schema' });
|
const schemaLog = log.child({ namespace: 'schema' });
|
||||||
|
|
||||||
@ -29,12 +27,13 @@ export const receivedOpenAPISchema = createAsyncThunk(
|
|||||||
'nodes/receivedOpenAPISchema',
|
'nodes/receivedOpenAPISchema',
|
||||||
async (_, { dispatch, rejectWithValue }) => {
|
async (_, { dispatch, rejectWithValue }) => {
|
||||||
try {
|
try {
|
||||||
const dereferencedSchema = (await SwaggerParser.dereference(
|
const response = await fetch(`openapi.json`);
|
||||||
'openapi.json'
|
const openAPISchema = await response.json();
|
||||||
)) as OpenAPIV3.Document;
|
|
||||||
|
schemaLog.info({ openAPISchema }, 'Received OpenAPI schema');
|
||||||
|
|
||||||
const schemaJSON = JSON.parse(
|
const schemaJSON = JSON.parse(
|
||||||
JSON.stringify(dereferencedSchema, getCircularReplacer())
|
JSON.stringify(openAPISchema, getCircularReplacer())
|
||||||
);
|
);
|
||||||
|
|
||||||
return schemaJSON;
|
return schemaJSON;
|
||||||
|
24
invokeai/frontend/web/src/theme/colors/colors.ts
Normal file
24
invokeai/frontend/web/src/theme/colors/colors.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { InvokeAIThemeColors } from 'theme/themeTypes';
|
||||||
|
import { generateColorPalette } from 'theme/util/generateColorPalette';
|
||||||
|
|
||||||
|
const BASE = { H: 220, S: 16 };
|
||||||
|
const ACCENT = { H: 250, S: 52 };
|
||||||
|
const WORKING = { H: 47, S: 50 };
|
||||||
|
const WARNING = { H: 28, S: 50 };
|
||||||
|
const OK = { H: 113, S: 50 };
|
||||||
|
const ERROR = { H: 0, S: 50 };
|
||||||
|
|
||||||
|
export const InvokeAIColors: InvokeAIThemeColors = {
|
||||||
|
base: generateColorPalette(BASE.H, BASE.S),
|
||||||
|
baseAlpha: generateColorPalette(BASE.H, BASE.S, true),
|
||||||
|
accent: generateColorPalette(ACCENT.H, ACCENT.S),
|
||||||
|
accentAlpha: generateColorPalette(ACCENT.H, ACCENT.S, true),
|
||||||
|
working: generateColorPalette(WORKING.H, WORKING.S),
|
||||||
|
workingAlpha: generateColorPalette(WORKING.H, WORKING.S, true),
|
||||||
|
warning: generateColorPalette(WARNING.H, WARNING.S),
|
||||||
|
warningAlpha: generateColorPalette(WARNING.H, WARNING.S, true),
|
||||||
|
ok: generateColorPalette(OK.H, OK.S),
|
||||||
|
okAlpha: generateColorPalette(OK.H, OK.S, true),
|
||||||
|
error: generateColorPalette(ERROR.H, ERROR.S),
|
||||||
|
errorAlpha: generateColorPalette(ERROR.H, ERROR.S, true),
|
||||||
|
};
|
@ -1,18 +0,0 @@
|
|||||||
import { InvokeAIThemeColors } from 'theme/themeTypes';
|
|
||||||
import { generateColorPalette } from '../util/generateColorPalette';
|
|
||||||
|
|
||||||
export const greenTeaThemeColors: InvokeAIThemeColors = {
|
|
||||||
base: generateColorPalette(223, 10),
|
|
||||||
baseAlpha: generateColorPalette(223, 10, false, true),
|
|
||||||
accent: generateColorPalette(160, 60),
|
|
||||||
accentAlpha: generateColorPalette(160, 60, false, true),
|
|
||||||
working: generateColorPalette(47, 68),
|
|
||||||
workingAlpha: generateColorPalette(47, 68, false, true),
|
|
||||||
warning: generateColorPalette(28, 75),
|
|
||||||
warningAlpha: generateColorPalette(28, 75, false, true),
|
|
||||||
ok: generateColorPalette(122, 49),
|
|
||||||
okAlpha: generateColorPalette(122, 49, false, true),
|
|
||||||
error: generateColorPalette(0, 50),
|
|
||||||
errorAlpha: generateColorPalette(0, 50, false, true),
|
|
||||||
gridLineColor: 'rgba(255, 255, 255, 0.15)',
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { InvokeAIThemeColors } from 'theme/themeTypes';
|
|
||||||
import { generateColorPalette } from 'theme/util/generateColorPalette';
|
|
||||||
|
|
||||||
export const invokeAIThemeColors: InvokeAIThemeColors = {
|
|
||||||
base: generateColorPalette(220, 15),
|
|
||||||
baseAlpha: generateColorPalette(220, 15, false, true),
|
|
||||||
accent: generateColorPalette(250, 50),
|
|
||||||
accentAlpha: generateColorPalette(250, 50, false, true),
|
|
||||||
working: generateColorPalette(47, 67),
|
|
||||||
workingAlpha: generateColorPalette(47, 67, false, true),
|
|
||||||
warning: generateColorPalette(28, 75),
|
|
||||||
warningAlpha: generateColorPalette(28, 75, false, true),
|
|
||||||
ok: generateColorPalette(113, 70),
|
|
||||||
okAlpha: generateColorPalette(113, 70, false, true),
|
|
||||||
error: generateColorPalette(0, 76),
|
|
||||||
errorAlpha: generateColorPalette(0, 76, false, true),
|
|
||||||
gridLineColor: 'rgba(150, 150, 180, 0.15)',
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { InvokeAIThemeColors } from 'theme/themeTypes';
|
|
||||||
import { generateColorPalette } from '../util/generateColorPalette';
|
|
||||||
|
|
||||||
export const lightThemeColors: InvokeAIThemeColors = {
|
|
||||||
base: generateColorPalette(223, 10, true),
|
|
||||||
baseAlpha: generateColorPalette(223, 10, true, true),
|
|
||||||
accent: generateColorPalette(40, 80, true),
|
|
||||||
accentAlpha: generateColorPalette(40, 80, true, true),
|
|
||||||
working: generateColorPalette(47, 68, true),
|
|
||||||
workingAlpha: generateColorPalette(47, 68, true, true),
|
|
||||||
warning: generateColorPalette(28, 75, true),
|
|
||||||
warningAlpha: generateColorPalette(28, 75, true, true),
|
|
||||||
ok: generateColorPalette(122, 49, true),
|
|
||||||
okAlpha: generateColorPalette(122, 49, true, true),
|
|
||||||
error: generateColorPalette(0, 50, true),
|
|
||||||
errorAlpha: generateColorPalette(0, 50, true, true),
|
|
||||||
gridLineColor: 'rgba(0, 0, 0, 0.15)',
|
|
||||||
};
|
|
@ -1,18 +0,0 @@
|
|||||||
import { InvokeAIThemeColors } from 'theme/themeTypes';
|
|
||||||
import { generateColorPalette } from '../util/generateColorPalette';
|
|
||||||
|
|
||||||
export const oceanBlueColors: InvokeAIThemeColors = {
|
|
||||||
base: generateColorPalette(220, 30),
|
|
||||||
baseAlpha: generateColorPalette(220, 30, false, true),
|
|
||||||
accent: generateColorPalette(210, 80),
|
|
||||||
accentAlpha: generateColorPalette(210, 80, false, true),
|
|
||||||
working: generateColorPalette(47, 68),
|
|
||||||
workingAlpha: generateColorPalette(47, 68, false, true),
|
|
||||||
warning: generateColorPalette(28, 75),
|
|
||||||
warningAlpha: generateColorPalette(28, 75, false, true),
|
|
||||||
ok: generateColorPalette(122, 49),
|
|
||||||
okAlpha: generateColorPalette(122, 49, false, true),
|
|
||||||
error: generateColorPalette(0, 100),
|
|
||||||
errorAlpha: generateColorPalette(0, 100, false, true),
|
|
||||||
gridLineColor: 'rgba(136, 148, 184, 0.15)',
|
|
||||||
};
|
|
@ -3,6 +3,7 @@ import {
|
|||||||
createMultiStyleConfigHelpers,
|
createMultiStyleConfigHelpers,
|
||||||
defineStyle,
|
defineStyle,
|
||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { definePartsStyle, defineMultiStyleConfig } =
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
@ -18,16 +19,16 @@ const invokeAIButton = defineStyle((props) => {
|
|||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
bg: `${c}.800`,
|
bg: mode(`${c}.200`, `${c}.700`)(props),
|
||||||
color: 'base.100',
|
color: mode(`${c}.900`, `${c}.100`)(props),
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: `${c}.700`,
|
bg: mode(`${c}.250`, `${c}.650`)(props),
|
||||||
},
|
},
|
||||||
_expanded: {
|
_expanded: {
|
||||||
bg: `${c}.750`,
|
bg: mode(`${c}.250`, `${c}.650`)(props),
|
||||||
borderBottomRadius: 'none',
|
borderBottomRadius: 'none',
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: `${c}.700`,
|
bg: mode(`${c}.300`, `${c}.600`)(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -36,7 +37,7 @@ const invokeAIButton = defineStyle((props) => {
|
|||||||
const invokeAIPanel = defineStyle((props) => {
|
const invokeAIPanel = defineStyle((props) => {
|
||||||
const { colorScheme: c } = props;
|
const { colorScheme: c } = props;
|
||||||
return {
|
return {
|
||||||
bg: `${c}.800`,
|
bg: mode(`${c}.100`, `${c}.800`)(props),
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
borderTopRadius: 'none',
|
borderTopRadius: 'none',
|
||||||
};
|
};
|
||||||
|
@ -1,44 +1,117 @@
|
|||||||
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
|
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const invokeAI = defineStyle((props) => {
|
const invokeAI = defineStyle((props) => {
|
||||||
const { colorScheme: c } = props;
|
const { colorScheme: c } = props;
|
||||||
// must specify `_disabled` colors if we override `_hover`, else hover on disabled has no styles
|
// must specify `_disabled` colors if we override `_hover`, else hover on disabled has no styles
|
||||||
|
|
||||||
|
if (c === 'base') {
|
||||||
const _disabled = {
|
const _disabled = {
|
||||||
bg: `${c}.600`,
|
bg: mode('base.200', 'base.700')(props),
|
||||||
color: `${c}.100`,
|
color: mode('base.500', 'base.150')(props),
|
||||||
svg: {
|
svg: {
|
||||||
fill: `${c}.100`,
|
fill: mode('base.500', 'base.150')(props),
|
||||||
},
|
},
|
||||||
|
opacity: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bg: `${c}.700`,
|
bg: mode('base.200', 'base.600')(props),
|
||||||
color: `${c}.100`,
|
color: mode('base.850', 'base.100')(props),
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
|
textShadow: mode(
|
||||||
|
'0 0 0.3rem var(--invokeai-colors-base-50)',
|
||||||
|
'0 0 0.3rem var(--invokeai-colors-base-900)'
|
||||||
|
)(props),
|
||||||
svg: {
|
svg: {
|
||||||
fill: `${c}.100`,
|
fill: mode('base.850', 'base.100')(props),
|
||||||
|
filter: mode(
|
||||||
|
'drop-shadow(0px 0px 0.3rem var(--invokeai-colors-base-100))',
|
||||||
|
'drop-shadow(0px 0px 0.3rem var(--invokeai-colors-base-800))'
|
||||||
|
)(props),
|
||||||
},
|
},
|
||||||
_disabled,
|
_disabled,
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: `${c}.650`,
|
bg: mode('base.300', 'base.500')(props),
|
||||||
color: `${c}.50`,
|
color: mode('base.900', 'base.50')(props),
|
||||||
svg: {
|
svg: {
|
||||||
fill: `${c}.50`,
|
fill: mode('base.900', 'base.50')(props),
|
||||||
},
|
},
|
||||||
_disabled,
|
_disabled,
|
||||||
},
|
},
|
||||||
_checked: {
|
_checked: {
|
||||||
bg: 'accent.700',
|
bg: mode('accent.400', 'accent.600')(props),
|
||||||
color: 'accent.100',
|
color: mode('base.50', 'base.100')(props),
|
||||||
svg: {
|
svg: {
|
||||||
fill: 'accent.100',
|
fill: mode(`${c}.50`, `${c}.100`)(props),
|
||||||
|
filter: mode(
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))`
|
||||||
|
)(props),
|
||||||
},
|
},
|
||||||
_disabled,
|
_disabled,
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'accent.600',
|
bg: mode('accent.500', 'accent.500')(props),
|
||||||
color: 'accent.50',
|
color: mode('white', 'base.50')(props),
|
||||||
svg: {
|
svg: {
|
||||||
fill: 'accent.50',
|
fill: mode('white', 'base.50')(props),
|
||||||
|
},
|
||||||
|
_disabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const _disabled = {
|
||||||
|
bg: mode(`${c}.200`, `${c}.700`)(props),
|
||||||
|
color: mode(`${c}.100`, `${c}.150`)(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode(`${c}.100`, `${c}.150`)(props),
|
||||||
|
},
|
||||||
|
opacity: 1,
|
||||||
|
filter: mode(undefined, 'saturate(65%)')(props),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
bg: mode(`${c}.400`, `${c}.600`)(props),
|
||||||
|
color: mode(`base.50`, `base.100`)(props),
|
||||||
|
borderRadius: 'base',
|
||||||
|
textShadow: mode(
|
||||||
|
`0 0 0.3rem var(--invokeai-colors-${c}-600)`,
|
||||||
|
`0 0 0.3rem var(--invokeai-colors-${c}-900)`
|
||||||
|
)(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode(`base.50`, `base.100`)(props),
|
||||||
|
filter: mode(
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))`
|
||||||
|
)(props),
|
||||||
|
},
|
||||||
|
_disabled,
|
||||||
|
_hover: {
|
||||||
|
bg: mode(`${c}.500`, `${c}.500`)(props),
|
||||||
|
color: mode('white', `base.50`)(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode('white', `base.50`)(props),
|
||||||
|
},
|
||||||
|
_disabled,
|
||||||
|
},
|
||||||
|
_checked: {
|
||||||
|
bg: mode('accent.400', 'accent.600')(props),
|
||||||
|
color: mode('base.50', 'base.100')(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode(`base.50`, `base.100`)(props),
|
||||||
|
filter: mode(
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))`
|
||||||
|
)(props),
|
||||||
|
},
|
||||||
|
_disabled,
|
||||||
|
_hover: {
|
||||||
|
bg: mode('accent.500', 'accent.500')(props),
|
||||||
|
color: mode('white', 'base.50')(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode('white', 'base.50')(props),
|
||||||
},
|
},
|
||||||
_disabled,
|
_disabled,
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
createMultiStyleConfigHelpers,
|
createMultiStyleConfigHelpers,
|
||||||
defineStyle,
|
defineStyle,
|
||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { definePartsStyle, defineMultiStyleConfig } =
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
@ -11,14 +12,18 @@ const invokeAIControl = defineStyle((props) => {
|
|||||||
const { colorScheme: c } = props;
|
const { colorScheme: c } = props;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
bg: mode('base.200', 'base.700')(props),
|
||||||
|
borderColor: mode('base.200', 'base.700')(props),
|
||||||
|
color: mode('base.900', 'base.100')(props),
|
||||||
|
|
||||||
_checked: {
|
_checked: {
|
||||||
bg: `${c}.200`,
|
bg: mode(`${c}.300`, `${c}.600`)(props),
|
||||||
borderColor: `${c}.200`,
|
borderColor: mode(`${c}.300`, `${c}.600`)(props),
|
||||||
color: 'base.900',
|
color: mode(`${c}.900`, `${c}.100`)(props),
|
||||||
|
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: `${c}.300`,
|
bg: mode(`${c}.400`, `${c}.500`)(props),
|
||||||
borderColor: `${c}.300`,
|
borderColor: mode(`${c}.400`, `${c}.500`)(props),
|
||||||
},
|
},
|
||||||
|
|
||||||
_disabled: {
|
_disabled: {
|
||||||
@ -29,9 +34,9 @@ const invokeAIControl = defineStyle((props) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_indeterminate: {
|
_indeterminate: {
|
||||||
bg: `${c}.200`,
|
bg: mode(`${c}.300`, `${c}.600`)(props),
|
||||||
borderColor: `${c}.200`,
|
borderColor: mode(`${c}.300`, `${c}.600`)(props),
|
||||||
color: 'base.900',
|
color: mode(`${c}.900`, `${c}.100`)(props),
|
||||||
},
|
},
|
||||||
|
|
||||||
_disabled: {
|
_disabled: {
|
||||||
@ -44,7 +49,7 @@ const invokeAIControl = defineStyle((props) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_invalid: {
|
_invalid: {
|
||||||
borderColor: 'red.300',
|
borderColor: mode('error.600', 'error.300')(props),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
|
import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const invokeAI = defineStyle((_props) => {
|
const invokeAI = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
marginEnd: 0,
|
marginEnd: 0,
|
||||||
@ -12,7 +13,7 @@ const invokeAI = defineStyle((_props) => {
|
|||||||
_disabled: {
|
_disabled: {
|
||||||
opacity: 0.4,
|
opacity: 0.4,
|
||||||
},
|
},
|
||||||
color: 'base.300',
|
color: mode('base.700', 'base.300')(props),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,38 +1,40 @@
|
|||||||
import { menuAnatomy } from '@chakra-ui/anatomy';
|
import { menuAnatomy } from '@chakra-ui/anatomy';
|
||||||
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
|
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { definePartsStyle, defineMultiStyleConfig } =
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
createMultiStyleConfigHelpers(menuAnatomy.keys);
|
createMultiStyleConfigHelpers(menuAnatomy.keys);
|
||||||
|
|
||||||
// define the base component styles
|
// define the base component styles
|
||||||
const invokeAI = definePartsStyle({
|
const invokeAI = definePartsStyle((props) => ({
|
||||||
// define the part you're going to style
|
// define the part you're going to style
|
||||||
button: {
|
button: {
|
||||||
// this will style the MenuButton component
|
// this will style the MenuButton component
|
||||||
fontWeight: '600',
|
fontWeight: 500,
|
||||||
bg: 'base.500',
|
bg: mode('base.300', 'base.500')(props),
|
||||||
color: 'base.200',
|
color: mode('base.900', 'base.100')(props),
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'base.600',
|
bg: mode('base.400', 'base.600')(props),
|
||||||
color: 'white',
|
color: mode('base.900', 'base.50')(props),
|
||||||
|
fontWeight: 600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: {
|
list: {
|
||||||
zIndex: 9999,
|
zIndex: 9999,
|
||||||
bg: 'base.800',
|
bg: mode('base.200', 'base.800')(props),
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
// this will style the MenuItem and MenuItemOption components
|
// this will style the MenuItem and MenuItemOption components
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
bg: 'base.800',
|
bg: mode('base.200', 'base.800')(props),
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'base.750',
|
bg: mode('base.300', 'base.700')(props),
|
||||||
},
|
},
|
||||||
_focus: {
|
_focus: {
|
||||||
bg: 'base.700',
|
bg: mode('base.400', 'base.600')(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
}));
|
||||||
|
|
||||||
export const menuTheme = defineMultiStyleConfig({
|
export const menuTheme = defineMultiStyleConfig({
|
||||||
variants: {
|
variants: {
|
||||||
|
@ -3,28 +3,31 @@ import {
|
|||||||
createMultiStyleConfigHelpers,
|
createMultiStyleConfigHelpers,
|
||||||
defineStyle,
|
defineStyle,
|
||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { defineMultiStyleConfig, definePartsStyle } =
|
const { defineMultiStyleConfig, definePartsStyle } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
|
|
||||||
const invokeAIOverlay = defineStyle({
|
const invokeAIOverlay = defineStyle((props) => ({
|
||||||
bg: 'blackAlpha.600',
|
bg: mode('blackAlpha.700', 'blackAlpha.700')(props),
|
||||||
});
|
}));
|
||||||
|
|
||||||
const invokeAIDialogContainer = defineStyle({});
|
const invokeAIDialogContainer = defineStyle({});
|
||||||
|
|
||||||
const invokeAIDialog = defineStyle((_props) => {
|
const invokeAIDialog = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
bg: 'base.850',
|
layerStyle: 'first',
|
||||||
maxH: '80vh',
|
maxH: '80vh',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const invokeAIHeader = defineStyle((_props) => {
|
const invokeAIHeader = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
fontSize: 'lg',
|
fontSize: 'lg',
|
||||||
color: 'base.200',
|
layerStyle: 'first',
|
||||||
|
borderTopRadius: 'base',
|
||||||
|
borderInlineEndRadius: 'base',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -37,7 +40,7 @@ const invokeAIBody = defineStyle({
|
|||||||
const invokeAIFooter = defineStyle({});
|
const invokeAIFooter = defineStyle({});
|
||||||
|
|
||||||
export const invokeAI = definePartsStyle((props) => ({
|
export const invokeAI = definePartsStyle((props) => ({
|
||||||
overlay: invokeAIOverlay,
|
overlay: invokeAIOverlay(props),
|
||||||
dialogContainer: invokeAIDialogContainer,
|
dialogContainer: invokeAIDialogContainer,
|
||||||
dialog: invokeAIDialog(props),
|
dialog: invokeAIDialog(props),
|
||||||
header: invokeAIHeader(props),
|
header: invokeAIHeader(props),
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
|
|
||||||
import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
|
import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { defineMultiStyleConfig, definePartsStyle } =
|
const { defineMultiStyleConfig, definePartsStyle } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
@ -33,7 +34,7 @@ const invokeAIStepperGroup = defineStyle((_props) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const invokeAIStepper = defineStyle((_props) => {
|
const invokeAIStepper = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
border: 'none',
|
border: 'none',
|
||||||
// expand arrow hitbox
|
// expand arrow hitbox
|
||||||
@ -43,11 +44,11 @@ const invokeAIStepper = defineStyle((_props) => {
|
|||||||
my: 0,
|
my: 0,
|
||||||
|
|
||||||
svg: {
|
svg: {
|
||||||
color: 'base.300',
|
color: mode('base.700', 'base.300')(props),
|
||||||
width: 2.5,
|
width: 2.5,
|
||||||
height: 2.5,
|
height: 2.5,
|
||||||
_hover: {
|
_hover: {
|
||||||
color: 'base.50',
|
color: mode('base.900', 'base.100')(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
createMultiStyleConfigHelpers,
|
createMultiStyleConfigHelpers,
|
||||||
defineStyle,
|
defineStyle,
|
||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
import { cssVar } from '@chakra-ui/theme-tools';
|
import { cssVar, mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { defineMultiStyleConfig, definePartsStyle } =
|
const { defineMultiStyleConfig, definePartsStyle } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
@ -12,15 +12,20 @@ const $popperBg = cssVar('popper-bg');
|
|||||||
const $arrowBg = cssVar('popper-arrow-bg');
|
const $arrowBg = cssVar('popper-arrow-bg');
|
||||||
const $arrowShadowColor = cssVar('popper-arrow-shadow-color');
|
const $arrowShadowColor = cssVar('popper-arrow-shadow-color');
|
||||||
|
|
||||||
const invokeAIContent = defineStyle((_props) => {
|
const invokeAIContent = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
[$arrowBg.variable]: `colors.base.800`,
|
[$arrowBg.variable]: mode('colors.base.100', 'colors.base.800')(props),
|
||||||
[$popperBg.variable]: `colors.base.800`,
|
[$popperBg.variable]: mode('colors.base.100', 'colors.base.800')(props),
|
||||||
[$arrowShadowColor.variable]: `colors.base.600`,
|
[$arrowShadowColor.variable]: mode(
|
||||||
|
'colors.base.400',
|
||||||
|
'colors.base.600'
|
||||||
|
)(props),
|
||||||
minW: 'unset',
|
minW: 'unset',
|
||||||
width: 'unset',
|
width: 'unset',
|
||||||
p: 4,
|
p: 4,
|
||||||
bg: 'base.800',
|
bg: mode('base.100', 'base.800')(props),
|
||||||
|
border: 'none',
|
||||||
|
shadow: 'dark-lg',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { selectAnatomy as parts } from '@chakra-ui/anatomy';
|
import { selectAnatomy as parts } from '@chakra-ui/anatomy';
|
||||||
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
|
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
|
||||||
import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
|
import { getInputOutlineStyles } from '../util/getInputOutlineStyles';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { definePartsStyle, defineMultiStyleConfig } =
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
|
|
||||||
const invokeAIIcon = defineStyle((_props) => {
|
const invokeAIIcon = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
color: 'base.300',
|
color: mode('base.200', 'base.300')(props),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { sliderAnatomy as parts } from '@chakra-ui/anatomy';
|
import { sliderAnatomy as parts } from '@chakra-ui/anatomy';
|
||||||
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
|
import { createMultiStyleConfigHelpers, defineStyle } from '@chakra-ui/react';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { definePartsStyle, defineMultiStyleConfig } =
|
const { definePartsStyle, defineMultiStyleConfig } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
|
|
||||||
const invokeAITrack = defineStyle((_props) => {
|
const invokeAITrack = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
bg: 'base.400',
|
bg: mode('base.400', 'base.600')(props),
|
||||||
h: 1.5,
|
h: 1.5,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -14,23 +15,24 @@ const invokeAITrack = defineStyle((_props) => {
|
|||||||
const invokeAIFilledTrack = defineStyle((props) => {
|
const invokeAIFilledTrack = defineStyle((props) => {
|
||||||
const { colorScheme: c } = props;
|
const { colorScheme: c } = props;
|
||||||
return {
|
return {
|
||||||
bg: `${c}.600`,
|
bg: mode(`${c}.400`, `${c}.600`)(props),
|
||||||
h: 1.5,
|
h: 1.5,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const invokeAIThumb = defineStyle((_props) => {
|
const invokeAIThumb = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
w: 2,
|
w: 2,
|
||||||
h: 4,
|
h: 4,
|
||||||
|
bg: mode('base.50', 'base.100')(props),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const invokeAIMark = defineStyle((_props) => {
|
const invokeAIMark = defineStyle((props) => {
|
||||||
return {
|
return {
|
||||||
fontSize: 'xs',
|
fontSize: 'xs',
|
||||||
fontWeight: '500',
|
fontWeight: '500',
|
||||||
color: 'base.400',
|
color: mode('base.700', 'base.400')(props),
|
||||||
mt: 2,
|
mt: 2,
|
||||||
insetInlineStart: 'unset',
|
insetInlineStart: 'unset',
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
createMultiStyleConfigHelpers,
|
createMultiStyleConfigHelpers,
|
||||||
defineStyle,
|
defineStyle,
|
||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { defineMultiStyleConfig, definePartsStyle } =
|
const { defineMultiStyleConfig, definePartsStyle } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
@ -11,13 +12,13 @@ const invokeAITrack = defineStyle((props) => {
|
|||||||
const { colorScheme: c } = props;
|
const { colorScheme: c } = props;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bg: 'base.600',
|
bg: mode('base.300', 'base.600')(props),
|
||||||
|
|
||||||
_focusVisible: {
|
_focusVisible: {
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
},
|
},
|
||||||
_checked: {
|
_checked: {
|
||||||
bg: `${c}.600`,
|
bg: mode(`${c}.400`, `${c}.500`)(props),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -26,7 +27,7 @@ const invokeAIThumb = defineStyle((props) => {
|
|||||||
const { colorScheme: c } = props;
|
const { colorScheme: c } = props;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bg: `${c}.50`,
|
bg: mode(`${c}.50`, `${c}.50`)(props),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
createMultiStyleConfigHelpers,
|
createMultiStyleConfigHelpers,
|
||||||
defineStyle,
|
defineStyle,
|
||||||
} from '@chakra-ui/styled-system';
|
} from '@chakra-ui/styled-system';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const { defineMultiStyleConfig, definePartsStyle } =
|
const { defineMultiStyleConfig, definePartsStyle } =
|
||||||
createMultiStyleConfigHelpers(parts.keys);
|
createMultiStyleConfigHelpers(parts.keys);
|
||||||
@ -16,30 +17,53 @@ const invokeAIRoot = defineStyle((_props) => {
|
|||||||
|
|
||||||
const invokeAITab = defineStyle((_props) => ({}));
|
const invokeAITab = defineStyle((_props) => ({}));
|
||||||
|
|
||||||
const invokeAITablist = defineStyle((_props) => ({
|
const invokeAITablist = defineStyle((props) => {
|
||||||
|
const { colorScheme: c } = props;
|
||||||
|
|
||||||
|
return {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 1,
|
gap: 1,
|
||||||
color: 'base.700',
|
color: mode('base.700', 'base.400')(props),
|
||||||
button: {
|
button: {
|
||||||
fontSize: 'sm',
|
fontSize: 'sm',
|
||||||
padding: 2,
|
padding: 2,
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
|
textShadow: mode(
|
||||||
|
`0 0 0.3rem var(--invokeai-colors-accent-100)`,
|
||||||
|
`0 0 0.3rem var(--invokeai-colors-accent-900)`
|
||||||
|
)(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode('base.700', 'base.300')(props),
|
||||||
|
},
|
||||||
_selected: {
|
_selected: {
|
||||||
borderBottomColor: 'base.800',
|
bg: mode('accent.400', 'accent.600')(props),
|
||||||
bg: 'accent.700',
|
color: mode('base.50', 'base.100')(props),
|
||||||
color: 'accent.100',
|
svg: {
|
||||||
|
fill: mode(`base.50`, `base.100`)(props),
|
||||||
|
filter: mode(
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-600))`,
|
||||||
|
`drop-shadow(0px 0px 0.3rem var(--invokeai-colors-${c}-800))`
|
||||||
|
)(props),
|
||||||
|
},
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'accent.600',
|
bg: mode('accent.500', 'accent.500')(props),
|
||||||
color: 'accent.50',
|
color: mode('white', 'base.50')(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode('white', 'base.50')(props),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_hover: {
|
_hover: {
|
||||||
bg: 'base.600',
|
bg: mode('base.100', 'base.800')(props),
|
||||||
color: 'base.50',
|
color: mode('base.900', 'base.50')(props),
|
||||||
|
svg: {
|
||||||
|
fill: mode(`base.800`, `base.100`)(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const invokeAITabpanel = defineStyle((_props) => ({
|
const invokeAITabpanel = defineStyle((_props) => ({
|
||||||
padding: 0,
|
padding: 0,
|
||||||
@ -59,5 +83,6 @@ export const tabsTheme = defineMultiStyleConfig({
|
|||||||
},
|
},
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
variant: 'invokeAI',
|
variant: 'invokeAI',
|
||||||
|
colorScheme: 'accent',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
|
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
const subtext = defineStyle((_props) => ({
|
const subtext = defineStyle((props) => ({
|
||||||
color: 'base.400',
|
color: mode('colors.base.500', 'colors.base.400')(props),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const textTheme = defineStyleConfig({
|
export const textTheme = defineStyleConfig({
|
||||||
|
17
invokeai/frontend/web/src/theme/components/tooltip.ts
Normal file
17
invokeai/frontend/web/src/theme/components/tooltip.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { defineStyle, defineStyleConfig } from '@chakra-ui/react';
|
||||||
|
import { mode } from '@chakra-ui/theme-tools';
|
||||||
|
import { cssVar } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
|
const $arrowBg = cssVar('popper-arrow-bg');
|
||||||
|
|
||||||
|
// define the base component styles
|
||||||
|
const baseStyle = defineStyle((props) => ({
|
||||||
|
borderRadius: 'base',
|
||||||
|
shadow: 'dark-lg',
|
||||||
|
bg: mode('base.700', 'base.200')(props),
|
||||||
|
[$arrowBg.variable]: mode('colors.base.700', 'colors.base.200')(props),
|
||||||
|
pb: 1.5,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// export the component theme
|
||||||
|
export const tooltipTheme = defineStyleConfig({ baseStyle });
|
@ -1,7 +1,6 @@
|
|||||||
import { ThemeOverride } from '@chakra-ui/react';
|
import { ThemeOverride } from '@chakra-ui/react';
|
||||||
import type { StyleFunctionProps } from '@chakra-ui/styled-system';
|
|
||||||
|
|
||||||
import { invokeAIThemeColors } from 'theme/colors/invokeAI';
|
import { InvokeAIColors } from './colors/colors';
|
||||||
import { accordionTheme } from './components/accordion';
|
import { accordionTheme } from './components/accordion';
|
||||||
import { buttonTheme } from './components/button';
|
import { buttonTheme } from './components/button';
|
||||||
import { checkboxTheme } from './components/checkbox';
|
import { checkboxTheme } from './components/checkbox';
|
||||||
@ -12,13 +11,14 @@ import { modalTheme } from './components/modal';
|
|||||||
import { numberInputTheme } from './components/numberInput';
|
import { numberInputTheme } from './components/numberInput';
|
||||||
import { popoverTheme } from './components/popover';
|
import { popoverTheme } from './components/popover';
|
||||||
import { progressTheme } from './components/progress';
|
import { progressTheme } from './components/progress';
|
||||||
import { no_scrollbar, scrollbar as _scrollbar } from './components/scrollbar';
|
import { no_scrollbar } from './components/scrollbar';
|
||||||
import { selectTheme } from './components/select';
|
import { selectTheme } from './components/select';
|
||||||
import { sliderTheme } from './components/slider';
|
import { sliderTheme } from './components/slider';
|
||||||
import { switchTheme } from './components/switch';
|
import { switchTheme } from './components/switch';
|
||||||
import { tabsTheme } from './components/tabs';
|
import { tabsTheme } from './components/tabs';
|
||||||
import { textTheme } from './components/text';
|
import { textTheme } from './components/text';
|
||||||
import { textareaTheme } from './components/textarea';
|
import { textareaTheme } from './components/textarea';
|
||||||
|
import { tooltipTheme } from './components/tooltip';
|
||||||
|
|
||||||
export const theme: ThemeOverride = {
|
export const theme: ThemeOverride = {
|
||||||
config: {
|
config: {
|
||||||
@ -26,30 +26,32 @@ export const theme: ThemeOverride = {
|
|||||||
initialColorMode: 'dark',
|
initialColorMode: 'dark',
|
||||||
useSystemColorMode: false,
|
useSystemColorMode: false,
|
||||||
},
|
},
|
||||||
styles: {
|
layerStyles: {
|
||||||
global: (_props: StyleFunctionProps) => ({
|
|
||||||
body: {
|
body: {
|
||||||
bg: 'base.900',
|
bg: 'base.50',
|
||||||
color: 'base.50',
|
color: 'base.900',
|
||||||
overflow: {
|
'.chakra-ui-dark &': { bg: 'base.900', color: 'base.50' },
|
||||||
base: 'scroll',
|
},
|
||||||
xl: 'hidden',
|
first: {
|
||||||
|
bg: 'base.100',
|
||||||
|
color: 'base.900',
|
||||||
|
'.chakra-ui-dark &': { bg: 'base.850', color: 'base.100' },
|
||||||
|
},
|
||||||
|
second: {
|
||||||
|
bg: 'base.200',
|
||||||
|
color: 'base.900',
|
||||||
|
'.chakra-ui-dark &': { bg: 'base.800', color: 'base.100' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
styles: {
|
||||||
|
global: () => ({
|
||||||
|
layerStyle: 'body',
|
||||||
'*': { ...no_scrollbar },
|
'*': { ...no_scrollbar },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
direction: 'ltr',
|
direction: 'ltr',
|
||||||
fonts: {
|
fonts: {
|
||||||
body: `'InterVariable', sans-serif`,
|
body: `'Inter Variable', sans-serif`,
|
||||||
},
|
|
||||||
breakpoints: {
|
|
||||||
base: '0em', // 0px and onwards
|
|
||||||
sm: '30em', // 480px and onwards
|
|
||||||
md: '48em', // 768px and onwards
|
|
||||||
lg: '62em', // 992px and onwards
|
|
||||||
xl: '80em', // 1280px and onwards
|
|
||||||
'2xl': '96em', // 1536px and onwards
|
|
||||||
},
|
},
|
||||||
shadows: {
|
shadows: {
|
||||||
light: {
|
light: {
|
||||||
@ -68,9 +70,7 @@ export const theme: ThemeOverride = {
|
|||||||
},
|
},
|
||||||
nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`,
|
nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`,
|
||||||
},
|
},
|
||||||
colors: {
|
colors: InvokeAIColors,
|
||||||
...invokeAIThemeColors,
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
Button: buttonTheme, // Button and IconButton
|
Button: buttonTheme, // Button and IconButton
|
||||||
Input: inputTheme,
|
Input: inputTheme,
|
||||||
@ -88,5 +88,6 @@ export const theme: ThemeOverride = {
|
|||||||
Checkbox: checkboxTheme,
|
Checkbox: checkboxTheme,
|
||||||
Menu: menuTheme,
|
Menu: menuTheme,
|
||||||
Text: textTheme,
|
Text: textTheme,
|
||||||
|
Tooltip: tooltipTheme,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,6 @@ export type InvokeAIThemeColors = {
|
|||||||
okAlpha: Partial<InvokeAIPaletteSteps>;
|
okAlpha: Partial<InvokeAIPaletteSteps>;
|
||||||
error: Partial<InvokeAIPaletteSteps>;
|
error: Partial<InvokeAIPaletteSteps>;
|
||||||
errorAlpha: Partial<InvokeAIPaletteSteps>;
|
errorAlpha: Partial<InvokeAIPaletteSteps>;
|
||||||
gridLineColor: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InvokeAIPaletteSteps = {
|
export type InvokeAIPaletteSteps = {
|
||||||
|
@ -2,46 +2,35 @@ import { InvokeAIPaletteSteps } from 'theme/themeTypes';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add two numbers together
|
* Add two numbers together
|
||||||
* @param {String | Number} hue Hue of the color (0-360) - Reds 0, Greens 120, Blues 240
|
* @param {String | Number} H Hue of the color (0-360) - Reds 0, Greens 120, Blues 240
|
||||||
* @param {String | Number} saturation Saturation of the color (0-100)
|
* @param {String | Number} L Saturation of the color (0-100)
|
||||||
* @param {boolean} light True to generate light color palette
|
* @param {Boolean} alpha Whether or not to generate this palette as a transparency palette
|
||||||
*/
|
*/
|
||||||
export function generateColorPalette(
|
export function generateColorPalette(
|
||||||
hue: string | number,
|
H: string | number,
|
||||||
saturation: string | number,
|
S: string | number,
|
||||||
light = false,
|
|
||||||
alpha = false
|
alpha = false
|
||||||
) {
|
) {
|
||||||
hue = String(hue);
|
H = String(H);
|
||||||
saturation = String(saturation);
|
S = String(S);
|
||||||
|
|
||||||
const colorSteps = Array.from({ length: 21 }, (_, i) => i * 50);
|
const colorSteps = Array.from({ length: 21 }, (_, i) => i * 50);
|
||||||
|
|
||||||
const lightnessSteps = [
|
const lightnessSteps = [
|
||||||
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 59, 64, 68, 73, 77, 82, 86,
|
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 59, 64, 68, 73, 77, 82, 86,
|
||||||
95, 100,
|
95, 100,
|
||||||
];
|
];
|
||||||
|
|
||||||
const darkPalette: Partial<InvokeAIPaletteSteps> = {};
|
const p = colorSteps.reduce((palette, step, index) => {
|
||||||
const lightPalette: Partial<InvokeAIPaletteSteps> = {};
|
|
||||||
|
|
||||||
colorSteps.forEach((colorStep, index) => {
|
|
||||||
const A = alpha ? lightnessSteps[index] / 100 : 1;
|
const A = alpha ? lightnessSteps[index] / 100 : 1;
|
||||||
|
|
||||||
// Lightness should be 50% for alpha colors
|
// Lightness should be 50% for alpha colors
|
||||||
const darkPaletteLightness = alpha
|
const L = alpha ? 50 : lightnessSteps[colorSteps.length - 1 - index];
|
||||||
? 50
|
|
||||||
: lightnessSteps[colorSteps.length - 1 - index];
|
|
||||||
|
|
||||||
darkPalette[
|
palette[step as keyof typeof palette] = `hsl(${H} ${S}% ${L}% / ${A})`;
|
||||||
colorStep as keyof typeof darkPalette
|
|
||||||
] = `hsl(${hue} ${saturation}% ${darkPaletteLightness}% / ${A})`;
|
|
||||||
|
|
||||||
const lightPaletteLightness = alpha ? 50 : lightnessSteps[index];
|
return palette;
|
||||||
|
}, {} as InvokeAIPaletteSteps);
|
||||||
|
|
||||||
lightPalette[
|
return p;
|
||||||
colorStep as keyof typeof lightPalette
|
|
||||||
] = `hsl(${hue} ${saturation}% ${lightPaletteLightness}% / ${A})`;
|
|
||||||
});
|
|
||||||
|
|
||||||
return light ? lightPalette : darkPalette;
|
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
import { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
import { StyleFunctionProps, mode } from '@chakra-ui/theme-tools';
|
||||||
|
|
||||||
export const getInputOutlineStyles = (_props?: StyleFunctionProps) => ({
|
export const getInputOutlineStyles = (props: StyleFunctionProps) => ({
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderColor: 'base.800',
|
borderColor: mode('base.200', 'base.800')(props),
|
||||||
bg: 'base.900',
|
bg: mode('base.50', 'base.900')(props),
|
||||||
borderRadius: 'base',
|
borderRadius: 'base',
|
||||||
color: 'base.100',
|
color: mode('base.900', 'base.100')(props),
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
_hover: {
|
_hover: {
|
||||||
borderColor: 'base.600',
|
borderColor: mode('base.300', 'base.600')(props),
|
||||||
},
|
},
|
||||||
_focus: {
|
_focus: {
|
||||||
borderColor: 'accent.700',
|
borderColor: mode('accent.200', 'accent.600')(props),
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
_hover: {
|
_hover: {
|
||||||
borderColor: 'accent.600',
|
borderColor: mode('accent.300', 'accent.500')(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_invalid: {
|
_invalid: {
|
||||||
borderColor: 'error.700',
|
borderColor: mode('error.300', 'error.600')(props),
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
_hover: {
|
_hover: {
|
||||||
borderColor: 'error.600',
|
borderColor: mode('error.400', 'error.500')(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_disabled: {
|
_disabled: {
|
||||||
borderColor: 'base.700',
|
borderColor: mode('base.300', 'base.700')(props),
|
||||||
bg: 'base.700',
|
bg: mode('base.300', 'base.700')(props),
|
||||||
color: 'base.400',
|
color: mode('base.600', 'base.400')(props),
|
||||||
_hover: {
|
_hover: {
|
||||||
borderColor: 'base.700',
|
borderColor: mode('base.300', 'base.700')(props),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_placeholder: {
|
_placeholder: {
|
||||||
color: 'base.500',
|
color: mode('base.700', 'base.400')(props),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
3
invokeai/frontend/web/src/theme/util/mode.ts
Normal file
3
invokeai/frontend/web/src/theme/util/mode.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const mode =
|
||||||
|
(light: string, dark: string) => (colorMode: 'light' | 'dark') =>
|
||||||
|
colorMode === 'light' ? light : dark;
|
@ -11,7 +11,8 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Node",
|
"moduleResolution": "Node",
|
||||||
"resolveJsonModule": true,
|
// TODO: Disabled for IDE performance issues with our translation JSON
|
||||||
|
// "resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user