mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
20 Commits
feat/js/dy
...
feat/many-
Author | SHA1 | Date | |
---|---|---|---|
704cfd8ff5 | |||
2990fa23fe | |||
58cb5fefd0 | |||
2ef5919475 | |||
7f07528b08 | |||
a2f944a657 | |||
0317cc158a | |||
8648332b4f | |||
c2aee42fa3 | |||
a77f6b0c18 | |||
8771e32ed2 | |||
5e1ed63076 | |||
cad358dc9a | |||
8501ca0843 | |||
560a59123a | |||
62b700b908 | |||
9aedf84ac2 | |||
a08179bf34 | |||
0b9aaf1b0b | |||
da98f281ee |
@ -1,9 +1,9 @@
|
|||||||
from fastapi import Body, HTTPException, Path, Query
|
from fastapi import Body, HTTPException, Path
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
from invokeai.app.services.board_record_storage import BoardRecord, BoardChanges
|
|
||||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
from invokeai.app.models.image import (AddManyImagesToBoardResult,
|
||||||
from invokeai.app.services.models.board_record import BoardDTO
|
GetAllBoardImagesForBoardResult,
|
||||||
from invokeai.app.services.models.image_record import ImageDTO
|
RemoveManyImagesFromBoardResult)
|
||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"])
|
|||||||
|
|
||||||
|
|
||||||
@board_images_router.post(
|
@board_images_router.post(
|
||||||
"/",
|
"/{board_id}",
|
||||||
operation_id="create_board_image",
|
operation_id="create_board_image",
|
||||||
responses={
|
responses={
|
||||||
201: {"description": "The image was added to a board successfully"},
|
201: {"description": "The image was added to a board successfully"},
|
||||||
@ -19,16 +19,19 @@ board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"])
|
|||||||
status_code=201,
|
status_code=201,
|
||||||
)
|
)
|
||||||
async def create_board_image(
|
async def create_board_image(
|
||||||
board_id: str = Body(description="The id of the board to add to"),
|
board_id: str = Path(description="The id of the board to add to"),
|
||||||
image_name: str = Body(description="The name of the image to add"),
|
image_name: str = Body(description="The name of the image to add"),
|
||||||
):
|
):
|
||||||
"""Creates a board_image"""
|
"""Creates a board_image"""
|
||||||
try:
|
try:
|
||||||
result = ApiDependencies.invoker.services.board_images.add_image_to_board(board_id=board_id, image_name=image_name)
|
result = ApiDependencies.invoker.services.board_images.add_image_to_board(
|
||||||
|
board_id=board_id, image_name=image_name
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail="Failed to add to board")
|
raise HTTPException(status_code=500, detail="Failed to add to board")
|
||||||
|
|
||||||
|
|
||||||
@board_images_router.delete(
|
@board_images_router.delete(
|
||||||
"/",
|
"/",
|
||||||
operation_id="remove_board_image",
|
operation_id="remove_board_image",
|
||||||
@ -38,32 +41,78 @@ async def create_board_image(
|
|||||||
status_code=201,
|
status_code=201,
|
||||||
)
|
)
|
||||||
async def remove_board_image(
|
async def remove_board_image(
|
||||||
board_id: str = Body(description="The id of the board"),
|
image_name: str = Body(
|
||||||
image_name: str = Body(description="The name of the image to remove"),
|
description="The name of the image to remove from its board"
|
||||||
|
),
|
||||||
):
|
):
|
||||||
"""Deletes a board_image"""
|
"""Deletes a board_image"""
|
||||||
try:
|
try:
|
||||||
result = ApiDependencies.invoker.services.board_images.remove_image_from_board(board_id=board_id, image_name=image_name)
|
result = ApiDependencies.invoker.services.board_images.remove_image_from_board(
|
||||||
|
image_name=image_name
|
||||||
|
)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail="Failed to update board")
|
raise HTTPException(status_code=500, detail="Failed to update board")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@board_images_router.get(
|
@board_images_router.get(
|
||||||
"/{board_id}",
|
"/{board_id}",
|
||||||
operation_id="list_board_images",
|
operation_id="get_all_board_images_for_board",
|
||||||
response_model=OffsetPaginatedResults[ImageDTO],
|
response_model=GetAllBoardImagesForBoardResult,
|
||||||
)
|
)
|
||||||
async def list_board_images(
|
async def get_all_board_images_for_board(
|
||||||
board_id: str = Path(description="The id of the board"),
|
board_id: str = Path(description="The id of the board"),
|
||||||
offset: int = Query(default=0, description="The page offset"),
|
) -> GetAllBoardImagesForBoardResult:
|
||||||
limit: int = Query(default=10, description="The number of boards per page"),
|
"""Gets all image names for a board"""
|
||||||
) -> OffsetPaginatedResults[ImageDTO]:
|
|
||||||
"""Gets a list of images for a board"""
|
|
||||||
|
|
||||||
results = ApiDependencies.invoker.services.board_images.get_images_for_board(
|
result = (
|
||||||
board_id,
|
ApiDependencies.invoker.services.board_images.get_all_board_images_for_board(
|
||||||
|
board_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@board_images_router.patch(
|
||||||
|
"/{board_id}/images",
|
||||||
|
operation_id="create_multiple_board_images",
|
||||||
|
responses={
|
||||||
|
201: {"description": "The images were added to the board successfully"},
|
||||||
|
},
|
||||||
|
status_code=201,
|
||||||
|
)
|
||||||
|
async def create_multiple_board_images(
|
||||||
|
board_id: str = Path(description="The id of the board"),
|
||||||
|
image_names: list[str] = Body(
|
||||||
|
description="The names of the images to add to the board"
|
||||||
|
),
|
||||||
|
) -> AddManyImagesToBoardResult:
|
||||||
|
"""Add many images to a board"""
|
||||||
|
|
||||||
|
results = ApiDependencies.invoker.services.board_images.add_many_images_to_board(
|
||||||
|
board_id, image_names
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@board_images_router.post(
|
||||||
|
"/images",
|
||||||
|
operation_id="delete_multiple_board_images",
|
||||||
|
responses={
|
||||||
|
201: {"description": "The images were removed from their boards successfully"},
|
||||||
|
},
|
||||||
|
status_code=201,
|
||||||
|
)
|
||||||
|
async def delete_multiple_board_images(
|
||||||
|
image_names: list[str] = Body(
|
||||||
|
description="The names of the images to remove from their boards, if they have one"
|
||||||
|
),
|
||||||
|
) -> RemoveManyImagesFromBoardResult:
|
||||||
|
"""Remove many images from their boards, if they have one"""
|
||||||
|
|
||||||
|
results = (
|
||||||
|
ApiDependencies.invoker.services.board_images.remove_many_images_from_board(
|
||||||
|
image_names
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from fastapi import Body, HTTPException, Path, Query
|
from fastapi import Body, HTTPException, Path, Query
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
|
|
||||||
|
from invokeai.app.models.image import DeleteManyImagesResult
|
||||||
from invokeai.app.services.board_record_storage import BoardChanges
|
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"])
|
||||||
@ -69,25 +71,26 @@ async def update_board(
|
|||||||
raise HTTPException(status_code=500, detail="Failed to update board")
|
raise HTTPException(status_code=500, detail="Failed to update board")
|
||||||
|
|
||||||
|
|
||||||
@boards_router.delete("/{board_id}", operation_id="delete_board")
|
@boards_router.delete("/{board_id}", operation_id="delete_board", response_model=DeleteManyImagesResult)
|
||||||
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(
|
include_images: Optional[bool] = Query(
|
||||||
description="Permanently delete all images on the board", default=False
|
description="Permanently delete all images on the board", default=False
|
||||||
),
|
),
|
||||||
) -> None:
|
) -> DeleteManyImagesResult:
|
||||||
"""Deletes a board"""
|
"""Deletes a board"""
|
||||||
try:
|
try:
|
||||||
if include_images is True:
|
if include_images is True:
|
||||||
ApiDependencies.invoker.services.images.delete_images_on_board(
|
result = ApiDependencies.invoker.services.images.delete_images_on_board(
|
||||||
board_id=board_id
|
board_id=board_id
|
||||||
)
|
)
|
||||||
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
||||||
else:
|
else:
|
||||||
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
||||||
|
result = DeleteManyImagesResult(deleted_images=[])
|
||||||
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# TODO: Does this need any exception handling at all?
|
raise HTTPException(status_code=500, detail="Failed to delete images on board")
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@boards_router.get(
|
@boards_router.get(
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
import io
|
import io
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile
|
|
||||||
from fastapi.routing import APIRouter
|
from fastapi import (Body, HTTPException, Path, Query, Request, Response,
|
||||||
|
UploadFile)
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
from fastapi.routing import APIRouter
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from invokeai.app.models.image import (
|
|
||||||
ImageCategory,
|
from invokeai.app.models.image import (DeleteManyImagesResult, ImageCategory,
|
||||||
ResourceOrigin,
|
ResourceOrigin)
|
||||||
)
|
|
||||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
||||||
from invokeai.app.services.models.image_record import (
|
from invokeai.app.services.models.image_record import (GetImagesByNamesResult,
|
||||||
ImageDTO,
|
ImageDTO,
|
||||||
ImageRecordChanges,
|
ImageRecordChanges,
|
||||||
ImageUrlsDTO,
|
ImageUrlsDTO)
|
||||||
)
|
|
||||||
from invokeai.app.services.item_storage import PaginatedResults
|
|
||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
@ -22,7 +21,7 @@ images_router = APIRouter(prefix="/v1/images", tags=["images"])
|
|||||||
|
|
||||||
|
|
||||||
@images_router.post(
|
@images_router.post(
|
||||||
"/",
|
"/upload",
|
||||||
operation_id="upload_image",
|
operation_id="upload_image",
|
||||||
responses={
|
responses={
|
||||||
201: {"description": "The image was uploaded successfully"},
|
201: {"description": "The image was uploaded successfully"},
|
||||||
@ -103,14 +102,14 @@ async def update_image(
|
|||||||
|
|
||||||
|
|
||||||
@images_router.get(
|
@images_router.get(
|
||||||
"/{image_name}/metadata",
|
"/{image_name}",
|
||||||
operation_id="get_image_metadata",
|
operation_id="get_image",
|
||||||
response_model=ImageDTO,
|
response_model=ImageDTO,
|
||||||
)
|
)
|
||||||
async def get_image_metadata(
|
async def get_image_dto(
|
||||||
image_name: str = Path(description="The name of image to get"),
|
image_name: str = Path(description="The name of image to get"),
|
||||||
) -> ImageDTO:
|
) -> ImageDTO:
|
||||||
"""Gets an image's metadata"""
|
"""Gets an image's DTO"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return ApiDependencies.invoker.services.images.get_dto(image_name)
|
return ApiDependencies.invoker.services.images.get_dto(image_name)
|
||||||
@ -119,8 +118,8 @@ async def get_image_metadata(
|
|||||||
|
|
||||||
|
|
||||||
@images_router.get(
|
@images_router.get(
|
||||||
"/{image_name}",
|
"/{image_name}/full_size",
|
||||||
operation_id="get_image_full",
|
operation_id="get_image_full_size",
|
||||||
response_class=Response,
|
response_class=Response,
|
||||||
responses={
|
responses={
|
||||||
200: {
|
200: {
|
||||||
@ -130,7 +129,7 @@ async def get_image_metadata(
|
|||||||
404: {"description": "Image not found"},
|
404: {"description": "Image not found"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def get_image_full(
|
async def get_image_full_size(
|
||||||
image_name: str = Path(description="The name of full-resolution image file to get"),
|
image_name: str = Path(description="The name of full-resolution image file to get"),
|
||||||
) -> FileResponse:
|
) -> FileResponse:
|
||||||
"""Gets a full-resolution image file"""
|
"""Gets a full-resolution image file"""
|
||||||
@ -208,10 +207,10 @@ async def get_image_urls(
|
|||||||
|
|
||||||
@images_router.get(
|
@images_router.get(
|
||||||
"/",
|
"/",
|
||||||
operation_id="list_images_with_metadata",
|
operation_id="get_many_images",
|
||||||
response_model=OffsetPaginatedResults[ImageDTO],
|
response_model=OffsetPaginatedResults[ImageDTO],
|
||||||
)
|
)
|
||||||
async def list_images_with_metadata(
|
async def get_many_images(
|
||||||
image_origin: Optional[ResourceOrigin] = Query(
|
image_origin: Optional[ResourceOrigin] = Query(
|
||||||
default=None, description="The origin of images to list"
|
default=None, description="The origin of images to list"
|
||||||
),
|
),
|
||||||
@ -222,7 +221,8 @@ async def list_images_with_metadata(
|
|||||||
default=None, description="Whether to list intermediate images"
|
default=None, description="Whether to list intermediate images"
|
||||||
),
|
),
|
||||||
board_id: Optional[str] = Query(
|
board_id: Optional[str] = Query(
|
||||||
default=None, description="The board id to filter by"
|
default=None,
|
||||||
|
description="The board id to filter by, provide 'none' for images without a board",
|
||||||
),
|
),
|
||||||
offset: int = Query(default=0, description="The page offset"),
|
offset: int = Query(default=0, description="The page offset"),
|
||||||
limit: int = Query(default=10, description="The number of images per page"),
|
limit: int = Query(default=10, description="The number of images per page"),
|
||||||
@ -239,3 +239,36 @@ async def list_images_with_metadata(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return image_dtos
|
return image_dtos
|
||||||
|
|
||||||
|
|
||||||
|
@images_router.post(
|
||||||
|
"/",
|
||||||
|
operation_id="get_images_by_names",
|
||||||
|
response_model=GetImagesByNamesResult,
|
||||||
|
)
|
||||||
|
async def get_images_by_names(
|
||||||
|
image_names: list[str] = Body(description="The names of the images to get"),
|
||||||
|
) -> GetImagesByNamesResult:
|
||||||
|
"""Gets a list of images"""
|
||||||
|
|
||||||
|
result = ApiDependencies.invoker.services.images.get_images_by_names(
|
||||||
|
image_names
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@images_router.post(
|
||||||
|
"/delete",
|
||||||
|
operation_id="delete_many_images",
|
||||||
|
response_model=DeleteManyImagesResult,
|
||||||
|
)
|
||||||
|
async def delete_many_images(
|
||||||
|
image_names: list[str] = Body(description="The names of the images to delete"),
|
||||||
|
) -> DeleteManyImagesResult:
|
||||||
|
"""Deletes many images"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
return ApiDependencies.invoker.services.images.delete_many(image_names)
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to delete images")
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from invokeai.app.util.metaenum import MetaEnum
|
from invokeai.app.util.metaenum import MetaEnum
|
||||||
@ -88,3 +89,41 @@ class ProgressImage(BaseModel):
|
|||||||
width: int = Field(description="The effective width of the image in pixels")
|
width: int = Field(description="The effective width of the image in pixels")
|
||||||
height: int = Field(description="The effective height of the image in pixels")
|
height: int = Field(description="The effective height of the image in pixels")
|
||||||
dataURL: str = Field(description="The image data as a b64 data URL")
|
dataURL: str = Field(description="The image data as a b64 data URL")
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteManyImagesResult(BaseModel):
|
||||||
|
"""The result of a delete many image operation."""
|
||||||
|
|
||||||
|
deleted_images: list[str] = Field(
|
||||||
|
description="The names of the images that were successfully deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AddManyImagesToBoardResult(BaseModel):
|
||||||
|
"""The result of an add many images to board operation."""
|
||||||
|
|
||||||
|
board_id: str = Field(description="The id of the board the images were added to")
|
||||||
|
added_images: list[str] = Field(
|
||||||
|
description="The names of the images that were successfully added"
|
||||||
|
)
|
||||||
|
total: int = Field(description="The total number of images on the board")
|
||||||
|
|
||||||
|
|
||||||
|
class RemoveManyImagesFromBoardResult(BaseModel):
|
||||||
|
"""The result of a remove many images from their boards operation."""
|
||||||
|
|
||||||
|
removed_images: list[str] = Field(
|
||||||
|
description="The names of the images that were successfully removed from their boards"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GetAllBoardImagesForBoardResult(BaseModel):
|
||||||
|
"""The result of a get all image names for board operation."""
|
||||||
|
|
||||||
|
board_id: str = Field(
|
||||||
|
description="The id of the board with which the images are associated"
|
||||||
|
)
|
||||||
|
image_names: list[str] = Field(
|
||||||
|
description="The names of the images that are associated with the board"
|
||||||
|
)
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from abc import ABC, abstractmethod
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import threading
|
import threading
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional, cast
|
from typing import Optional, cast
|
||||||
|
|
||||||
|
from invokeai.app.models.image import GetAllBoardImagesForBoardResult
|
||||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
||||||
from invokeai.app.services.models.image_record import (
|
from invokeai.app.services.models.image_record import (
|
||||||
ImageRecord,
|
ImageRecord, deserialize_image_record)
|
||||||
deserialize_image_record,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BoardImageRecordStorageBase(ABC):
|
class BoardImageRecordStorageBase(ABC):
|
||||||
@ -25,18 +24,17 @@ class BoardImageRecordStorageBase(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def remove_image_from_board(
|
def remove_image_from_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
|
||||||
image_name: str,
|
image_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Removes an image from a board."""
|
"""Removes an image from a board."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_images_for_board(
|
def get_all_board_images_for_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
board_id: str,
|
||||||
) -> OffsetPaginatedResults[ImageRecord]:
|
) -> GetAllBoardImagesForBoardResult:
|
||||||
"""Gets images for a board."""
|
"""Gets all image names for a board."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -154,7 +152,6 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
|
|||||||
|
|
||||||
def remove_image_from_board(
|
def remove_image_from_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
|
||||||
image_name: str,
|
image_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
@ -162,9 +159,9 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
|
|||||||
self._cursor.execute(
|
self._cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
DELETE FROM board_images
|
DELETE FROM board_images
|
||||||
WHERE board_id = ? AND image_name = ?;
|
WHERE image_name = ?;
|
||||||
""",
|
""",
|
||||||
(board_id, image_name),
|
(image_name,),
|
||||||
)
|
)
|
||||||
self._conn.commit()
|
self._conn.commit()
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
@ -173,42 +170,32 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
|
|||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
|
|
||||||
def get_images_for_board(
|
def get_all_board_images_for_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
board_id: str,
|
||||||
offset: int = 0,
|
) -> GetAllBoardImagesForBoardResult:
|
||||||
limit: int = 10,
|
|
||||||
) -> OffsetPaginatedResults[ImageRecord]:
|
|
||||||
# TODO: this isn't paginated yet?
|
|
||||||
try:
|
try:
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
self._cursor.execute(
|
self._cursor.execute(
|
||||||
"""--sql
|
"""--sql
|
||||||
SELECT images.*
|
SELECT image_name
|
||||||
FROM board_images
|
FROM board_images
|
||||||
INNER JOIN images ON board_images.image_name = images.image_name
|
WHERE board_id = ?
|
||||||
WHERE board_images.board_id = ?
|
ORDER BY updated_at DESC;
|
||||||
ORDER BY board_images.updated_at DESC;
|
|
||||||
""",
|
""",
|
||||||
(board_id,),
|
(board_id,),
|
||||||
)
|
)
|
||||||
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
|
||||||
images = list(map(lambda r: deserialize_image_record(dict(r)), result))
|
|
||||||
|
|
||||||
self._cursor.execute(
|
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||||
"""--sql
|
image_names = list(map(lambda r: r[0], result))
|
||||||
SELECT COUNT(*) FROM images WHERE 1=1;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
count = cast(int, self._cursor.fetchone()[0])
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
except sqlite3.Error as e:
|
||||||
self._conn.rollback()
|
self._conn.rollback()
|
||||||
raise e
|
raise e
|
||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
return OffsetPaginatedResults(
|
return GetAllBoardImagesForBoardResult(
|
||||||
items=images, offset=offset, limit=limit, total=count
|
board_id=board_id, image_names=image_names
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_board_for_image(
|
def get_board_for_image(
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
from typing import List, Union, Optional
|
from typing import List, Optional, Union
|
||||||
from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase
|
|
||||||
from invokeai.app.services.board_record_storage import (
|
|
||||||
BoardRecord,
|
|
||||||
BoardRecordStorageBase,
|
|
||||||
)
|
|
||||||
|
|
||||||
from invokeai.app.services.image_record_storage import (
|
from invokeai.app.models.image import (AddManyImagesToBoardResult,
|
||||||
ImageRecordStorageBase,
|
GetAllBoardImagesForBoardResult,
|
||||||
OffsetPaginatedResults,
|
RemoveManyImagesFromBoardResult)
|
||||||
)
|
from invokeai.app.services.board_image_record_storage import \
|
||||||
|
BoardImageRecordStorageBase
|
||||||
|
from invokeai.app.services.board_record_storage import (BoardRecord,
|
||||||
|
BoardRecordStorageBase)
|
||||||
|
from invokeai.app.services.image_record_storage import (ImageRecordStorageBase,
|
||||||
|
OffsetPaginatedResults)
|
||||||
from invokeai.app.services.models.board_record import BoardDTO
|
from invokeai.app.services.models.board_record import BoardDTO
|
||||||
from invokeai.app.services.models.image_record import ImageDTO, image_record_to_dto
|
from invokeai.app.services.models.image_record import (ImageDTO,
|
||||||
|
image_record_to_dto)
|
||||||
from invokeai.app.services.urls import UrlServiceBase
|
from invokeai.app.services.urls import UrlServiceBase
|
||||||
|
|
||||||
|
|
||||||
@ -25,24 +26,40 @@ class BoardImagesServiceABC(ABC):
|
|||||||
board_id: str,
|
board_id: str,
|
||||||
image_name: str,
|
image_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Adds an image to a board."""
|
"""Adds an image to a board. If the image is on a different board, it is removed from that board."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add_many_images_to_board(
|
||||||
|
self,
|
||||||
|
board_id: str,
|
||||||
|
image_names: list[str],
|
||||||
|
) -> AddManyImagesToBoardResult:
|
||||||
|
"""Adds many images to a board. If an image is on a different board, it is removed from that board."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def remove_image_from_board(
|
def remove_image_from_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
|
||||||
image_name: str,
|
image_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Removes an image from a board."""
|
"""Removes an image from its board."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_images_for_board(
|
def remove_many_images_from_board(
|
||||||
|
self,
|
||||||
|
image_names: list[str],
|
||||||
|
) -> RemoveManyImagesFromBoardResult:
|
||||||
|
"""Removes many images from their board, if they had one."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_all_board_images_for_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
board_id: str,
|
||||||
) -> OffsetPaginatedResults[ImageDTO]:
|
) -> GetAllBoardImagesForBoardResult:
|
||||||
"""Gets images for a board."""
|
"""Gets all image names for a board."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -91,37 +108,59 @@ class BoardImagesService(BoardImagesServiceABC):
|
|||||||
) -> None:
|
) -> None:
|
||||||
self._services.board_image_records.add_image_to_board(board_id, image_name)
|
self._services.board_image_records.add_image_to_board(board_id, image_name)
|
||||||
|
|
||||||
|
def add_many_images_to_board(
|
||||||
|
self,
|
||||||
|
board_id: str,
|
||||||
|
image_names: list[str],
|
||||||
|
) -> AddManyImagesToBoardResult:
|
||||||
|
added_images: list[str] = []
|
||||||
|
|
||||||
|
for image_name in image_names:
|
||||||
|
try:
|
||||||
|
self._services.board_image_records.add_image_to_board(
|
||||||
|
board_id, image_name
|
||||||
|
)
|
||||||
|
added_images.append(image_name)
|
||||||
|
except Exception as e:
|
||||||
|
self._services.logger.exception(e)
|
||||||
|
|
||||||
|
total = self._services.board_image_records.get_image_count_for_board(board_id)
|
||||||
|
|
||||||
|
return AddManyImagesToBoardResult(
|
||||||
|
board_id=board_id, added_images=added_images, total=total
|
||||||
|
)
|
||||||
|
|
||||||
def remove_image_from_board(
|
def remove_image_from_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
|
||||||
image_name: str,
|
image_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._services.board_image_records.remove_image_from_board(board_id, image_name)
|
self._services.board_image_records.remove_image_from_board(image_name)
|
||||||
|
|
||||||
def get_images_for_board(
|
def remove_many_images_from_board(
|
||||||
|
self,
|
||||||
|
image_names: list[str],
|
||||||
|
) -> RemoveManyImagesFromBoardResult:
|
||||||
|
removed_images: list[str] = []
|
||||||
|
|
||||||
|
for image_name in image_names:
|
||||||
|
try:
|
||||||
|
self._services.board_image_records.remove_image_from_board(image_name)
|
||||||
|
removed_images.append(image_name)
|
||||||
|
except Exception as e:
|
||||||
|
self._services.logger.exception(e)
|
||||||
|
|
||||||
|
return RemoveManyImagesFromBoardResult(
|
||||||
|
removed_images=removed_images,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_all_board_images_for_board(
|
||||||
self,
|
self,
|
||||||
board_id: str,
|
board_id: str,
|
||||||
) -> OffsetPaginatedResults[ImageDTO]:
|
) -> GetAllBoardImagesForBoardResult:
|
||||||
image_records = self._services.board_image_records.get_images_for_board(
|
result = self._services.board_image_records.get_all_board_images_for_board(
|
||||||
board_id
|
board_id
|
||||||
)
|
)
|
||||||
image_dtos = list(
|
return result
|
||||||
map(
|
|
||||||
lambda r: image_record_to_dto(
|
|
||||||
r,
|
|
||||||
self._services.urls.get_image_url(r.image_name),
|
|
||||||
self._services.urls.get_image_url(r.image_name, True),
|
|
||||||
board_id,
|
|
||||||
),
|
|
||||||
image_records.items,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return OffsetPaginatedResults[ImageDTO](
|
|
||||||
items=image_dtos,
|
|
||||||
offset=image_records.offset,
|
|
||||||
limit=image_records.limit,
|
|
||||||
total=image_records.total,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_board_for_image(
|
def get_board_for_image(
|
||||||
self,
|
self,
|
||||||
@ -136,7 +175,7 @@ def board_record_to_dto(
|
|||||||
) -> BoardDTO:
|
) -> BoardDTO:
|
||||||
"""Converts a board record to a board DTO."""
|
"""Converts a board record to a board DTO."""
|
||||||
return BoardDTO(
|
return BoardDTO(
|
||||||
**board_record.dict(exclude={'cover_image_name'}),
|
**board_record.dict(exclude={"cover_image_name"}),
|
||||||
cover_image_name=cover_image_name,
|
cover_image_name=cover_image_name,
|
||||||
image_count=image_count,
|
image_count=image_count,
|
||||||
)
|
)
|
||||||
|
@ -80,6 +80,11 @@ class ImageRecordStorageBase(ABC):
|
|||||||
"""Gets a page of image records."""
|
"""Gets a page of image records."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_by_names(self, image_names: list[str]) -> list[ImageRecord]:
|
||||||
|
"""Gets a list of image records by name."""
|
||||||
|
pass
|
||||||
|
|
||||||
# TODO: The database has a nullable `deleted_at` column, currently unused.
|
# TODO: The database has a nullable `deleted_at` column, currently unused.
|
||||||
# Should we implement soft deletes? Would need coordination with ImageFileStorage.
|
# Should we implement soft deletes? Would need coordination with ImageFileStorage.
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -329,11 +334,15 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
query_params.append(is_intermediate)
|
query_params.append(is_intermediate)
|
||||||
|
|
||||||
if board_id is not None:
|
if board_id is not None:
|
||||||
query_conditions += """--sql
|
if board_id == "none":
|
||||||
AND board_images.board_id = ?
|
query_conditions += """--sql
|
||||||
"""
|
AND board_images.board_id IS NULL
|
||||||
|
"""
|
||||||
query_params.append(board_id)
|
else:
|
||||||
|
query_conditions += """--sql
|
||||||
|
AND board_images.board_id = ?
|
||||||
|
"""
|
||||||
|
query_params.append(board_id)
|
||||||
|
|
||||||
query_pagination = """--sql
|
query_pagination = """--sql
|
||||||
ORDER BY images.created_at DESC LIMIT ? OFFSET ?
|
ORDER BY images.created_at DESC LIMIT ? OFFSET ?
|
||||||
@ -365,6 +374,30 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
items=images, offset=offset, limit=limit, total=count
|
items=images, offset=offset, limit=limit, total=count
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_by_names(self, image_names: list[str]) -> list[ImageRecord]:
|
||||||
|
try:
|
||||||
|
placeholders = ",".join("?" for _ in image_names)
|
||||||
|
|
||||||
|
self._lock.acquire()
|
||||||
|
|
||||||
|
# Construct the SQLite query with the placeholders
|
||||||
|
query = f"""--sql
|
||||||
|
SELECT * FROM images
|
||||||
|
WHERE image_name IN ({placeholders})
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Execute the query with the list of IDs as parameters
|
||||||
|
self._cursor.execute(query, image_names)
|
||||||
|
|
||||||
|
result = cast(list[sqlite3.Row], self._cursor.fetchall())
|
||||||
|
images = list(map(lambda r: deserialize_image_record(dict(r)), result))
|
||||||
|
return images
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
self._conn.rollback()
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
self._lock.release()
|
||||||
|
|
||||||
def delete(self, image_name: str) -> None:
|
def delete(self, image_name: str) -> None:
|
||||||
try:
|
try:
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
@ -465,9 +498,7 @@ class SqliteImageRecordStorage(ImageRecordStorageBase):
|
|||||||
finally:
|
finally:
|
||||||
self._lock.release()
|
self._lock.release()
|
||||||
|
|
||||||
def get_most_recent_image_for_board(
|
def get_most_recent_image_for_board(self, board_id: str) -> Optional[ImageRecord]:
|
||||||
self, board_id: str
|
|
||||||
) -> Optional[ImageRecord]:
|
|
||||||
try:
|
try:
|
||||||
self._lock.acquire()
|
self._lock.acquire()
|
||||||
self._cursor.execute(
|
self._cursor.execute(
|
||||||
|
@ -1,37 +1,27 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
from typing import Optional, TYPE_CHECKING, Union
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from PIL.Image import Image as PILImageType
|
from PIL.Image import Image as PILImageType
|
||||||
|
|
||||||
from invokeai.app.models.image import (
|
from invokeai.app.models.image import (DeleteManyImagesResult, ImageCategory,
|
||||||
ImageCategory,
|
InvalidImageCategoryException,
|
||||||
ResourceOrigin,
|
InvalidOriginException, ResourceOrigin)
|
||||||
InvalidImageCategoryException,
|
|
||||||
InvalidOriginException,
|
|
||||||
)
|
|
||||||
from invokeai.app.models.metadata import ImageMetadata
|
from invokeai.app.models.metadata import ImageMetadata
|
||||||
from invokeai.app.services.board_image_record_storage import BoardImageRecordStorageBase
|
from invokeai.app.services.board_image_record_storage import \
|
||||||
from invokeai.app.services.image_record_storage import (
|
BoardImageRecordStorageBase
|
||||||
ImageRecordDeleteException,
|
|
||||||
ImageRecordNotFoundException,
|
|
||||||
ImageRecordSaveException,
|
|
||||||
ImageRecordStorageBase,
|
|
||||||
OffsetPaginatedResults,
|
|
||||||
)
|
|
||||||
from invokeai.app.services.models.image_record import (
|
|
||||||
ImageRecord,
|
|
||||||
ImageDTO,
|
|
||||||
ImageRecordChanges,
|
|
||||||
image_record_to_dto,
|
|
||||||
)
|
|
||||||
from invokeai.app.services.image_file_storage import (
|
from invokeai.app.services.image_file_storage import (
|
||||||
ImageFileDeleteException,
|
ImageFileDeleteException, ImageFileNotFoundException,
|
||||||
ImageFileNotFoundException,
|
ImageFileSaveException, ImageFileStorageBase)
|
||||||
ImageFileSaveException,
|
from invokeai.app.services.image_record_storage import (
|
||||||
ImageFileStorageBase,
|
ImageRecordDeleteException, ImageRecordNotFoundException,
|
||||||
)
|
ImageRecordSaveException, ImageRecordStorageBase, OffsetPaginatedResults)
|
||||||
from invokeai.app.services.item_storage import ItemStorageABC, PaginatedResults
|
from invokeai.app.services.item_storage import ItemStorageABC
|
||||||
from invokeai.app.services.metadata import MetadataServiceBase
|
from invokeai.app.services.metadata import MetadataServiceBase
|
||||||
|
from invokeai.app.services.models.image_record import (GetImagesByNamesResult,
|
||||||
|
ImageDTO, ImageRecord,
|
||||||
|
ImageRecordChanges,
|
||||||
|
image_record_to_dto)
|
||||||
from invokeai.app.services.resource_name import NameServiceBase
|
from invokeai.app.services.resource_name import NameServiceBase
|
||||||
from invokeai.app.services.urls import UrlServiceBase
|
from invokeai.app.services.urls import UrlServiceBase
|
||||||
|
|
||||||
@ -107,13 +97,23 @@ class ImageServiceABC(ABC):
|
|||||||
"""Gets a paginated list of image DTOs."""
|
"""Gets a paginated list of image DTOs."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_images_by_names(self, image_names: list[str]) -> GetImagesByNamesResult:
|
||||||
|
"""Gets image DTOs by list of names."""
|
||||||
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete(self, image_name: str):
|
def delete(self, image_name: str):
|
||||||
"""Deletes an image."""
|
"""Deletes an image."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def delete_images_on_board(self, board_id: str):
|
def delete_many(self, image_names: list[str]) -> DeleteManyImagesResult:
|
||||||
|
"""Deletes many images."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def delete_images_on_board(self, board_id: str) -> DeleteManyImagesResult:
|
||||||
"""Deletes all images on a board."""
|
"""Deletes all images on a board."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -332,6 +332,28 @@ class ImageService(ImageServiceABC):
|
|||||||
self._services.logger.error("Problem getting paginated image DTOs")
|
self._services.logger.error("Problem getting paginated image DTOs")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
def get_images_by_names(self, image_names: list[str]) -> GetImagesByNamesResult:
|
||||||
|
try:
|
||||||
|
image_records = self._services.image_records.get_by_names(image_names)
|
||||||
|
|
||||||
|
image_dtos = list(
|
||||||
|
map(
|
||||||
|
lambda r: image_record_to_dto(
|
||||||
|
r,
|
||||||
|
self._services.urls.get_image_url(r.image_name),
|
||||||
|
self._services.urls.get_image_url(r.image_name, True),
|
||||||
|
self._services.board_image_records.get_board_for_image(
|
||||||
|
r.image_name
|
||||||
|
),
|
||||||
|
),
|
||||||
|
image_records,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return GetImagesByNamesResult(image_dtos=image_dtos)
|
||||||
|
except Exception as e:
|
||||||
|
self._services.logger.error("Problem getting image DTOs from names")
|
||||||
|
raise e
|
||||||
|
|
||||||
def delete(self, image_name: str):
|
def delete(self, image_name: str):
|
||||||
try:
|
try:
|
||||||
self._services.image_files.delete(image_name)
|
self._services.image_files.delete(image_name)
|
||||||
@ -346,18 +368,36 @@ 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):
|
def delete_many(self, image_names: list[str]) -> DeleteManyImagesResult:
|
||||||
|
deleted_images: list[str] = []
|
||||||
|
for image_name in image_names:
|
||||||
|
try:
|
||||||
|
self._services.image_files.delete(image_name)
|
||||||
|
self._services.image_records.delete(image_name)
|
||||||
|
deleted_images.append(image_name)
|
||||||
|
except ImageRecordDeleteException:
|
||||||
|
self._services.logger.error(f"Failed to delete image record")
|
||||||
|
deleted_images.append(image_name)
|
||||||
|
except ImageFileDeleteException:
|
||||||
|
self._services.logger.error(f"Failed to delete image file")
|
||||||
|
deleted_images.append(image_name)
|
||||||
|
except Exception as e:
|
||||||
|
self._services.logger.error("Problem deleting image record and file")
|
||||||
|
deleted_images.append(image_name)
|
||||||
|
return DeleteManyImagesResult(deleted_images=deleted_images)
|
||||||
|
|
||||||
|
def delete_images_on_board(self, board_id: str) -> DeleteManyImagesResult:
|
||||||
try:
|
try:
|
||||||
images = self._services.board_image_records.get_images_for_board(board_id)
|
board_images = (
|
||||||
image_name_list = list(
|
self._services.board_image_records.get_all_board_images_for_board(
|
||||||
map(
|
board_id
|
||||||
lambda r: r.image_name,
|
|
||||||
images.items,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
image_name_list = board_images.image_names
|
||||||
for image_name in image_name_list:
|
for image_name in image_name_list:
|
||||||
self._services.image_files.delete(image_name)
|
self._services.image_files.delete(image_name)
|
||||||
self._services.image_records.delete_many(image_name_list)
|
self._services.image_records.delete_many(image_name_list)
|
||||||
|
return DeleteManyImagesResult(deleted_images=board_images.image_names)
|
||||||
except ImageRecordDeleteException:
|
except ImageRecordDeleteException:
|
||||||
self._services.logger.error(f"Failed to delete image records")
|
self._services.logger.error(f"Failed to delete image records")
|
||||||
raise
|
raise
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr
|
from pydantic import BaseModel, Extra, Field, StrictBool, StrictStr
|
||||||
|
|
||||||
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
from invokeai.app.models.image import ImageCategory, ResourceOrigin
|
||||||
from invokeai.app.models.metadata import ImageMetadata
|
from invokeai.app.models.metadata import ImageMetadata
|
||||||
from invokeai.app.util.misc import get_iso_timestamp
|
from invokeai.app.util.misc import get_iso_timestamp
|
||||||
@ -95,8 +97,19 @@ class ImageDTO(ImageRecord, ImageUrlsDTO):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GetImagesByNamesResult(BaseModel):
|
||||||
|
"""The result of a get all image names for board operation."""
|
||||||
|
|
||||||
|
image_dtos: list[ImageDTO] = Field(
|
||||||
|
description="The names of the images that are associated with the board"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def image_record_to_dto(
|
def image_record_to_dto(
|
||||||
image_record: ImageRecord, image_url: str, thumbnail_url: str, board_id: Optional[str]
|
image_record: ImageRecord,
|
||||||
|
image_url: str,
|
||||||
|
thumbnail_url: str,
|
||||||
|
board_id: Optional[str],
|
||||||
) -> ImageDTO:
|
) -> ImageDTO:
|
||||||
"""Converts an image record to an image DTO."""
|
"""Converts an image record to an image DTO."""
|
||||||
return ImageDTO(
|
return ImageDTO(
|
||||||
|
@ -22,4 +22,4 @@ class LocalUrlService(UrlServiceBase):
|
|||||||
if thumbnail:
|
if thumbnail:
|
||||||
return f"{self._base_url}/images/{image_basename}/thumbnail"
|
return f"{self._base_url}/images/{image_basename}/thumbnail"
|
||||||
|
|
||||||
return f"{self._base_url}/images/{image_basename}"
|
return f"{self._base_url}/images/{image_basename}/full_size"
|
||||||
|
@ -128,13 +128,13 @@
|
|||||||
"@types/react-redux": "^7.1.25",
|
"@types/react-redux": "^7.1.25",
|
||||||
"@types/react-transition-group": "^4.4.6",
|
"@types/react-transition-group": "^4.4.6",
|
||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
"@typescript-eslint/parser": "^5.60.0",
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"babel-plugin-transform-imports": "^2.0.0",
|
"babel-plugin-transform-imports": "^2.0.0",
|
||||||
"concurrently": "^8.2.0",
|
"concurrently": "^8.2.0",
|
||||||
"eslint": "^8.43.0",
|
"eslint": "^8.44.0",
|
||||||
"eslint-config-prettier": "^8.8.0",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
@ -151,6 +151,8 @@
|
|||||||
"rollup-plugin-visualizer": "^5.9.2",
|
"rollup-plugin-visualizer": "^5.9.2",
|
||||||
"terser": "^5.18.1",
|
"terser": "^5.18.1",
|
||||||
"ts-toolbelt": "^9.6.0",
|
"ts-toolbelt": "^9.6.0",
|
||||||
|
"typescript": "^5.1.6",
|
||||||
|
"typescript-eslint": "^0.0.1-alpha.0",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.3.9",
|
||||||
"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",
|
||||||
|
@ -118,7 +118,7 @@
|
|||||||
"pinGallery": "Pin Gallery",
|
"pinGallery": "Pin Gallery",
|
||||||
"allImagesLoaded": "All Images Loaded",
|
"allImagesLoaded": "All Images Loaded",
|
||||||
"loadMore": "Load More",
|
"loadMore": "Load More",
|
||||||
"noImagesInGallery": "No Images In Gallery",
|
"noImagesInGallery": "No Images to Display",
|
||||||
"deleteImage": "Delete Image",
|
"deleteImage": "Delete Image",
|
||||||
"deleteImageBin": "Deleted images will be sent to your operating system's Bin.",
|
"deleteImageBin": "Deleted images will be sent to your operating system's Bin.",
|
||||||
"deleteImagePermanent": "Deleted images cannot be restored.",
|
"deleteImagePermanent": "Deleted images cannot be restored.",
|
||||||
|
@ -82,7 +82,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.dragData.payloadType === 'BATCH_SELECTION') {
|
if (props.dragData.payloadType === 'IMAGE_NAMES') {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -95,26 +95,7 @@ const DragPreview = (props: OverlayDragImageProps) => {
|
|||||||
...STYLES,
|
...STYLES,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Heading>{batchSelectionCount}</Heading>
|
<Heading>{props.dragData.payload.image_names.length}</Heading>
|
||||||
<Heading size="sm">Images</Heading>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.dragData.payloadType === 'GALLERY_SELECTION') {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
cursor: 'none',
|
|
||||||
userSelect: 'none',
|
|
||||||
position: 'relative',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexDir: 'column',
|
|
||||||
...STYLES,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Heading>{gallerySelectionCount}</Heading>
|
|
||||||
<Heading size="sm">Images</Heading>
|
<Heading size="sm">Images</Heading>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -6,18 +6,18 @@ import {
|
|||||||
useSensor,
|
useSensor,
|
||||||
useSensors,
|
useSensors,
|
||||||
} from '@dnd-kit/core';
|
} from '@dnd-kit/core';
|
||||||
|
import { snapCenterToCursor } from '@dnd-kit/modifiers';
|
||||||
|
import { dndDropped } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { AnimatePresence, motion } from 'framer-motion';
|
||||||
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
||||||
import DragPreview from './DragPreview';
|
import DragPreview from './DragPreview';
|
||||||
import { snapCenterToCursor } from '@dnd-kit/modifiers';
|
|
||||||
import { AnimatePresence, motion } from 'framer-motion';
|
|
||||||
import {
|
import {
|
||||||
DndContext,
|
DndContext,
|
||||||
DragEndEvent,
|
DragEndEvent,
|
||||||
DragStartEvent,
|
DragStartEvent,
|
||||||
TypesafeDraggableData,
|
TypesafeDraggableData,
|
||||||
} from './typesafeDnd';
|
} from './typesafeDnd';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import { imageDropped } from 'app/store/middleware/listenerMiddleware/listeners/imageDropped';
|
|
||||||
|
|
||||||
type ImageDndContextProps = PropsWithChildren;
|
type ImageDndContextProps = PropsWithChildren;
|
||||||
|
|
||||||
@ -42,18 +42,18 @@ const ImageDndContext = (props: ImageDndContextProps) => {
|
|||||||
if (!activeData || !overData) {
|
if (!activeData || !overData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(imageDropped({ overData, activeData }));
|
dispatch(dndDropped({ overData, activeData }));
|
||||||
setActiveDragData(null);
|
setActiveDragData(null);
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const mouseSensor = useSensor(MouseSensor, {
|
const mouseSensor = useSensor(MouseSensor, {
|
||||||
activationConstraint: { delay: 150, tolerance: 5 },
|
activationConstraint: { distance: 10 },
|
||||||
});
|
});
|
||||||
|
|
||||||
const touchSensor = useSensor(TouchSensor, {
|
const touchSensor = useSensor(TouchSensor, {
|
||||||
activationConstraint: { delay: 150, tolerance: 5 },
|
activationConstraint: { distance: 10 },
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Use KeyboardSensor - needs composition of multiple collisionDetection algos
|
// TODO: Use KeyboardSensor - needs composition of multiple collisionDetection algos
|
||||||
|
@ -77,18 +77,14 @@ export type ImageDraggableData = BaseDragData & {
|
|||||||
payload: { imageDTO: ImageDTO };
|
payload: { imageDTO: ImageDTO };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GallerySelectionDraggableData = BaseDragData & {
|
export type ImageNamesDraggableData = BaseDragData & {
|
||||||
payloadType: 'GALLERY_SELECTION';
|
payloadType: 'IMAGE_NAMES';
|
||||||
};
|
payload: { image_names: string[] };
|
||||||
|
|
||||||
export type BatchSelectionDraggableData = BaseDragData & {
|
|
||||||
payloadType: 'BATCH_SELECTION';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TypesafeDraggableData =
|
export type TypesafeDraggableData =
|
||||||
| ImageDraggableData
|
| ImageDraggableData
|
||||||
| GallerySelectionDraggableData
|
| ImageNamesDraggableData;
|
||||||
| BatchSelectionDraggableData;
|
|
||||||
|
|
||||||
interface UseDroppableTypesafeArguments
|
interface UseDroppableTypesafeArguments
|
||||||
extends Omit<UseDroppableArguments, 'data'> {
|
extends Omit<UseDroppableArguments, 'data'> {
|
||||||
@ -159,13 +155,11 @@ export const isValidDrop = (
|
|||||||
case 'SET_NODES_IMAGE':
|
case 'SET_NODES_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO';
|
return payloadType === 'IMAGE_DTO';
|
||||||
case 'SET_MULTI_NODES_IMAGE':
|
case 'SET_MULTI_NODES_IMAGE':
|
||||||
return payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION';
|
return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
|
||||||
case 'ADD_TO_BATCH':
|
case 'ADD_TO_BATCH':
|
||||||
return payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION';
|
return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
|
||||||
case 'MOVE_BOARD':
|
case 'MOVE_BOARD':
|
||||||
return (
|
return payloadType === 'IMAGE_DTO' || 'IMAGE_NAMES';
|
||||||
payloadType === 'IMAGE_DTO' || 'GALLERY_SELECTION' || 'BATCH_SELECTION'
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useDisclosure } from '@chakra-ui/react';
|
import { useDisclosure } from '@chakra-ui/react';
|
||||||
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
import { PropsWithChildren, createContext, useCallback, useState } from 'react';
|
||||||
|
import { useAddBoardImageMutation } from 'services/api/endpoints/boardImages';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { useAddImageToBoardMutation } from 'services/api/endpoints/boardImages';
|
|
||||||
|
|
||||||
export type ImageUsage = {
|
export type ImageUsage = {
|
||||||
isInitialImage: boolean;
|
isInitialImage: boolean;
|
||||||
@ -41,7 +41,7 @@ export const AddImageToBoardContextProvider = (props: Props) => {
|
|||||||
const [imageToMove, setImageToMove] = useState<ImageDTO>();
|
const [imageToMove, setImageToMove] = useState<ImageDTO>();
|
||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
const [addImageToBoard, result] = useAddImageToBoardMutation();
|
const [addImageToBoard, result] = useAddBoardImageMutation();
|
||||||
|
|
||||||
// Clean up after deleting or dismissing the modal
|
// Clean up after deleting or dismissing the modal
|
||||||
const closeAndClearImageToDelete = useCallback(() => {
|
const closeAndClearImageToDelete = useCallback(() => {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { initialBatchState } from 'features/batch/store/batchSlice';
|
||||||
import { initialCanvasState } from 'features/canvas/store/canvasSlice';
|
import { initialCanvasState } from 'features/canvas/store/canvasSlice';
|
||||||
import { initialControlNetState } from 'features/controlNet/store/controlNetSlice';
|
import { initialControlNetState } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { initialGalleryState } from 'features/gallery/store/gallerySlice';
|
import { initialGalleryState } from 'features/gallery/store/gallerySlice';
|
||||||
@ -17,6 +18,7 @@ const initialStates: {
|
|||||||
} = {
|
} = {
|
||||||
canvas: initialCanvasState,
|
canvas: initialCanvasState,
|
||||||
gallery: initialGalleryState,
|
gallery: initialGalleryState,
|
||||||
|
batch: initialBatchState,
|
||||||
generation: initialGenerationState,
|
generation: initialGenerationState,
|
||||||
lightbox: initialLightboxState,
|
lightbox: initialLightboxState,
|
||||||
nodes: initialNodesState,
|
nodes: initialNodesState,
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* This is a list of actions that should be excluded in the Redux DevTools.
|
||||||
|
*/
|
||||||
export const actionsDenylist = [
|
export const actionsDenylist = [
|
||||||
|
// very spammy canvas actions
|
||||||
'canvas/setCursorPosition',
|
'canvas/setCursorPosition',
|
||||||
'canvas/setStageCoordinates',
|
'canvas/setStageCoordinates',
|
||||||
'canvas/setStageScale',
|
'canvas/setStageScale',
|
||||||
@ -7,7 +11,11 @@ export const actionsDenylist = [
|
|||||||
'canvas/setBoundingBoxDimensions',
|
'canvas/setBoundingBoxDimensions',
|
||||||
'canvas/setIsDrawing',
|
'canvas/setIsDrawing',
|
||||||
'canvas/addPointToCurrentLine',
|
'canvas/addPointToCurrentLine',
|
||||||
|
// bazillions during generation
|
||||||
'socket/socketGeneratorProgress',
|
'socket/socketGeneratorProgress',
|
||||||
'socket/appSocketGeneratorProgress',
|
'socket/appSocketGeneratorProgress',
|
||||||
|
// every time user presses shift
|
||||||
'hotkeys/shiftKeyPressed',
|
'hotkeys/shiftKeyPressed',
|
||||||
|
// this happens after every state change
|
||||||
|
'@@REMEMBER_PERSISTED',
|
||||||
];
|
];
|
||||||
|
@ -7,6 +7,8 @@ import {
|
|||||||
} from '@reduxjs/toolkit';
|
} from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import type { AppDispatch, RootState } from '../../store';
|
import type { AppDispatch, RootState } from '../../store';
|
||||||
|
import { addBoardApiListeners } from './listeners/addBoardApiListeners';
|
||||||
|
import { addAddBoardToBatchListener } from './listeners/addBoardToBatch';
|
||||||
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
|
import { addCommitStagingAreaImageListener } from './listeners/addCommitStagingAreaImageListener';
|
||||||
import { addAppStartedListener } from './listeners/appStarted';
|
import { addAppStartedListener } from './listeners/appStarted';
|
||||||
import { addBoardIdSelectedListener } from './listeners/boardIdSelected';
|
import { addBoardIdSelectedListener } from './listeners/boardIdSelected';
|
||||||
@ -18,9 +20,9 @@ import { addCanvasSavedToGalleryListener } from './listeners/canvasSavedToGaller
|
|||||||
import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess';
|
import { addControlNetAutoProcessListener } from './listeners/controlNetAutoProcess';
|
||||||
import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed';
|
import { addControlNetImageProcessedListener } from './listeners/controlNetImageProcessed';
|
||||||
import {
|
import {
|
||||||
addImageAddedToBoardFulfilledListener,
|
addImageDTOReceivedFulfilledListener,
|
||||||
addImageAddedToBoardRejectedListener,
|
addImageDTOReceivedRejectedListener,
|
||||||
} from './listeners/imageAddedToBoard';
|
} from './listeners/imageDTOReceived';
|
||||||
import {
|
import {
|
||||||
addImageDeletedFulfilledListener,
|
addImageDeletedFulfilledListener,
|
||||||
addImageDeletedPendingListener,
|
addImageDeletedPendingListener,
|
||||||
@ -28,14 +30,6 @@ import {
|
|||||||
addRequestedImageDeletionListener,
|
addRequestedImageDeletionListener,
|
||||||
} from './listeners/imageDeleted';
|
} from './listeners/imageDeleted';
|
||||||
import { addImageDroppedListener } from './listeners/imageDropped';
|
import { addImageDroppedListener } from './listeners/imageDropped';
|
||||||
import {
|
|
||||||
addImageMetadataReceivedFulfilledListener,
|
|
||||||
addImageMetadataReceivedRejectedListener,
|
|
||||||
} from './listeners/imageMetadataReceived';
|
|
||||||
import {
|
|
||||||
addImageRemovedFromBoardFulfilledListener,
|
|
||||||
addImageRemovedFromBoardRejectedListener,
|
|
||||||
} from './listeners/imageRemovedFromBoard';
|
|
||||||
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
|
import { addImageToDeleteSelectedListener } from './listeners/imageToDeleteSelected';
|
||||||
import {
|
import {
|
||||||
addImageUpdatedFulfilledListener,
|
addImageUpdatedFulfilledListener,
|
||||||
@ -45,18 +39,11 @@ import {
|
|||||||
addImageUploadedFulfilledListener,
|
addImageUploadedFulfilledListener,
|
||||||
addImageUploadedRejectedListener,
|
addImageUploadedRejectedListener,
|
||||||
} from './listeners/imageUploaded';
|
} from './listeners/imageUploaded';
|
||||||
import {
|
import { addImagesLoadedListener } from './listeners/imagesLoaded';
|
||||||
addImageUrlsReceivedFulfilledListener,
|
|
||||||
addImageUrlsReceivedRejectedListener,
|
|
||||||
} from './listeners/imageUrlsReceived';
|
|
||||||
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
||||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||||
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
||||||
import {
|
import { addReceivedPageOfImagesListener } from './listeners/receivedPageOfImages';
|
||||||
addReceivedPageOfImagesFulfilledListener,
|
|
||||||
addReceivedPageOfImagesRejectedListener,
|
|
||||||
} from './listeners/receivedPageOfImages';
|
|
||||||
import { addSelectionAddedToBatchListener } from './listeners/selectionAddedToBatch';
|
|
||||||
import {
|
import {
|
||||||
addSessionCanceledFulfilledListener,
|
addSessionCanceledFulfilledListener,
|
||||||
addSessionCanceledPendingListener,
|
addSessionCanceledPendingListener,
|
||||||
@ -132,12 +119,8 @@ addRequestedBoardImageDeletionListener();
|
|||||||
addImageToDeleteSelectedListener();
|
addImageToDeleteSelectedListener();
|
||||||
|
|
||||||
// Image metadata
|
// Image metadata
|
||||||
addImageMetadataReceivedFulfilledListener();
|
addImageDTOReceivedFulfilledListener();
|
||||||
addImageMetadataReceivedRejectedListener();
|
addImageDTOReceivedRejectedListener();
|
||||||
|
|
||||||
// Image URLs
|
|
||||||
addImageUrlsReceivedFulfilledListener();
|
|
||||||
addImageUrlsReceivedRejectedListener();
|
|
||||||
|
|
||||||
// User Invoked
|
// User Invoked
|
||||||
addUserInvokedCanvasListener();
|
addUserInvokedCanvasListener();
|
||||||
@ -193,8 +176,8 @@ addSessionCanceledFulfilledListener();
|
|||||||
addSessionCanceledRejectedListener();
|
addSessionCanceledRejectedListener();
|
||||||
|
|
||||||
// Fetching images
|
// Fetching images
|
||||||
addReceivedPageOfImagesFulfilledListener();
|
addReceivedPageOfImagesListener();
|
||||||
addReceivedPageOfImagesRejectedListener();
|
addImagesLoadedListener();
|
||||||
|
|
||||||
// ControlNet
|
// ControlNet
|
||||||
addControlNetImageProcessedListener();
|
addControlNetImageProcessedListener();
|
||||||
@ -204,17 +187,15 @@ addControlNetAutoProcessListener();
|
|||||||
// addUpdateImageUrlsOnConnectListener();
|
// addUpdateImageUrlsOnConnectListener();
|
||||||
|
|
||||||
// Boards
|
// Boards
|
||||||
addImageAddedToBoardFulfilledListener();
|
addBoardApiListeners();
|
||||||
addImageAddedToBoardRejectedListener();
|
|
||||||
addImageRemovedFromBoardFulfilledListener();
|
|
||||||
addImageRemovedFromBoardRejectedListener();
|
|
||||||
addBoardIdSelectedListener();
|
addBoardIdSelectedListener();
|
||||||
|
|
||||||
// Node schemas
|
// Node schemas
|
||||||
addReceivedOpenAPISchemaListener();
|
addReceivedOpenAPISchemaListener();
|
||||||
|
|
||||||
// Batches
|
// Batches
|
||||||
addSelectionAddedToBatchListener();
|
// addSelectionAddedToBatchListener();
|
||||||
|
addAddBoardToBatchListener();
|
||||||
|
|
||||||
// DND
|
// DND
|
||||||
addImageDroppedListener();
|
addImageDroppedListener();
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
|
const moduleLog = log.child({ namespace: 'boards' });
|
||||||
|
|
||||||
|
export const addBoardApiListeners = () => {
|
||||||
|
// add image to board - fulfilled
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.addBoardImage.matchFulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { board_id, image_name } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { board_id, image_name } },
|
||||||
|
'Image added to board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// add image to board - rejected
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.addBoardImage.matchRejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { board_id, image_name } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { board_id, image_name } },
|
||||||
|
'Problem adding image to board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove image from board - fulfilled
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.deleteBoardImage.matchFulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { image_name } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug({ data: { image_name } }, 'Image removed from board');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove image from board - rejected
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.deleteBoardImage.matchRejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const image_name = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { image_name } },
|
||||||
|
'Problem removing image from board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// many images added to board - fulfilled
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.addManyBoardImages.matchFulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { board_id, image_names } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { board_id, image_names } },
|
||||||
|
'Images added to board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// many images added to board - rejected
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.addManyBoardImages.matchRejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { board_id, image_names } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { board_id, image_names } },
|
||||||
|
'Problem adding many images to board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove many images from board - fulfilled
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.deleteManyBoardImages.matchFulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { image_names } = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug({ data: { image_names } }, 'Images removed from board');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove many images from board - rejected
|
||||||
|
startAppListening({
|
||||||
|
matcher: boardImagesApi.endpoints.deleteManyBoardImages.matchRejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const image_names = action.meta.arg.originalArgs;
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { image_names } },
|
||||||
|
'Problem removing many images from board'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,31 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { imagesAddedToBatch } from 'features/batch/store/batchSlice';
|
||||||
|
import { boardImageNamesReceived } from 'services/api/thunks/boardImages';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
|
const moduleLog = log.child({ namespace: 'batch' });
|
||||||
|
|
||||||
|
export const boardAddedToBatch = createAction<{ board_id: string }>(
|
||||||
|
'batch/boardAddedToBatch'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const addAddBoardToBatchListener = () => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: boardAddedToBatch,
|
||||||
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
|
const { board_id } = action.payload;
|
||||||
|
|
||||||
|
const { requestId } = dispatch(boardImageNamesReceived({ board_id }));
|
||||||
|
|
||||||
|
const [{ payload }] = await take(
|
||||||
|
(
|
||||||
|
action
|
||||||
|
): action is ReturnType<typeof boardImageNamesReceived.fulfilled> =>
|
||||||
|
action.meta.requestId === requestId
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(imagesAddedToBatch(payload.image_names));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -1,9 +1,4 @@
|
|||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import {
|
|
||||||
INITIAL_IMAGE_LIMIT,
|
|
||||||
isLoadingChanged,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
export const appStarted = createAction('app/appStarted');
|
export const appStarted = createAction('app/appStarted');
|
||||||
@ -15,29 +10,27 @@ export const addAppStartedListener = () => {
|
|||||||
action,
|
action,
|
||||||
{ getState, dispatch, unsubscribe, cancelActiveListeners }
|
{ getState, dispatch, unsubscribe, cancelActiveListeners }
|
||||||
) => {
|
) => {
|
||||||
cancelActiveListeners();
|
// cancelActiveListeners();
|
||||||
unsubscribe();
|
// unsubscribe();
|
||||||
// fill up the gallery tab with images
|
// // fill up the gallery tab with images
|
||||||
await dispatch(
|
// await dispatch(
|
||||||
receivedPageOfImages({
|
// receivedPageOfImages({
|
||||||
categories: ['general'],
|
// categories: ['general'],
|
||||||
is_intermediate: false,
|
// is_intermediate: false,
|
||||||
offset: 0,
|
// offset: 0,
|
||||||
limit: INITIAL_IMAGE_LIMIT,
|
// // limit: INITIAL_IMAGE_LIMIT,
|
||||||
})
|
// })
|
||||||
);
|
// );
|
||||||
|
// // fill up the assets tab with images
|
||||||
// fill up the assets tab with images
|
// await dispatch(
|
||||||
await dispatch(
|
// receivedPageOfImages({
|
||||||
receivedPageOfImages({
|
// categories: ['control', 'mask', 'user', 'other'],
|
||||||
categories: ['control', 'mask', 'user', 'other'],
|
// is_intermediate: false,
|
||||||
is_intermediate: false,
|
// offset: 0,
|
||||||
offset: 0,
|
// // limit: INITIAL_IMAGE_LIMIT,
|
||||||
limit: INITIAL_IMAGE_LIMIT,
|
// })
|
||||||
})
|
// );
|
||||||
);
|
// dispatch(isLoadingChanged(false));
|
||||||
|
|
||||||
dispatch(isLoadingChanged(false));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,6 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
import {
|
|
||||||
imageSelected,
|
|
||||||
selectImagesAll,
|
|
||||||
boardIdSelected,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import {
|
|
||||||
IMAGES_PER_PAGE,
|
|
||||||
receivedPageOfImages,
|
|
||||||
} from 'services/api/thunks/image';
|
|
||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'boards' });
|
const moduleLog = log.child({ namespace: 'boards' });
|
||||||
|
|
||||||
@ -17,49 +8,40 @@ export const addBoardIdSelectedListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: boardIdSelected,
|
actionCreator: boardIdSelected,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
const board_id = action.payload;
|
// const board_id = action.payload;
|
||||||
|
// // we need to check if we need to fetch more images
|
||||||
// we need to check if we need to fetch more images
|
// const state = getState();
|
||||||
|
// const allImages = selectImagesAll(state);
|
||||||
const state = getState();
|
// if (!board_id) {
|
||||||
const allImages = selectImagesAll(state);
|
// // a board was unselected
|
||||||
|
// dispatch(imageSelected(allImages[0]?.image_name));
|
||||||
if (!board_id) {
|
// return;
|
||||||
// a board was unselected
|
// }
|
||||||
dispatch(imageSelected(allImages[0]?.image_name));
|
// const { categories } = state.gallery;
|
||||||
return;
|
// const filteredImages = allImages.filter((i) => {
|
||||||
}
|
// const isInCategory = categories.includes(i.image_category);
|
||||||
|
// const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
||||||
const { categories } = state.gallery;
|
// return isInCategory && isInSelectedBoard;
|
||||||
|
// });
|
||||||
const filteredImages = allImages.filter((i) => {
|
// // get the board from the cache
|
||||||
const isInCategory = categories.includes(i.image_category);
|
// const { data: boards } =
|
||||||
const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
// boardsApi.endpoints.listAllBoards.select()(state);
|
||||||
return isInCategory && isInSelectedBoard;
|
// const board = boards?.find((b) => b.board_id === board_id);
|
||||||
});
|
// if (!board) {
|
||||||
|
// // can't find the board in cache...
|
||||||
// get the board from the cache
|
// dispatch(imageSelected(allImages[0]?.image_name));
|
||||||
const { data: boards } =
|
// return;
|
||||||
boardsApi.endpoints.listAllBoards.select()(state);
|
// }
|
||||||
const board = boards?.find((b) => b.board_id === board_id);
|
// dispatch(imageSelected(board.cover_image_name ?? null));
|
||||||
|
// // if we haven't loaded one full page of images from this board, load more
|
||||||
if (!board) {
|
// if (
|
||||||
// can't find the board in cache...
|
// filteredImages.length < board.image_count &&
|
||||||
dispatch(imageSelected(allImages[0]?.image_name));
|
// filteredImages.length < IMAGES_PER_PAGE
|
||||||
return;
|
// ) {
|
||||||
}
|
// dispatch(
|
||||||
|
// receivedPageOfImages({ categories, board_id, is_intermediate: false })
|
||||||
dispatch(imageSelected(board.cover_image_name ?? null));
|
// );
|
||||||
|
// }
|
||||||
// if we haven't loaded one full page of images from this board, load more
|
|
||||||
if (
|
|
||||||
filteredImages.length < board.image_count &&
|
|
||||||
filteredImages.length < IMAGES_PER_PAGE
|
|
||||||
) {
|
|
||||||
dispatch(
|
|
||||||
receivedPageOfImages({ categories, board_id, is_intermediate: false })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -68,43 +50,36 @@ export const addBoardIdSelected_changeSelectedImage_listener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: boardIdSelected,
|
actionCreator: boardIdSelected,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
const board_id = action.payload;
|
// const board_id = action.payload;
|
||||||
|
// const state = getState();
|
||||||
const state = getState();
|
// // we need to check if we need to fetch more images
|
||||||
|
// if (!board_id) {
|
||||||
// we need to check if we need to fetch more images
|
// // a board was unselected - we don't need to do anything
|
||||||
|
// return;
|
||||||
if (!board_id) {
|
// }
|
||||||
// a board was unselected - we don't need to do anything
|
// const { categories } = state.gallery;
|
||||||
return;
|
// const filteredImages = selectImagesAll(state).filter((i) => {
|
||||||
}
|
// const isInCategory = categories.includes(i.image_category);
|
||||||
|
// const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
||||||
const { categories } = state.gallery;
|
// return isInCategory && isInSelectedBoard;
|
||||||
|
// });
|
||||||
const filteredImages = selectImagesAll(state).filter((i) => {
|
// // get the board from the cache
|
||||||
const isInCategory = categories.includes(i.image_category);
|
// const { data: boards } =
|
||||||
const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
// boardsApi.endpoints.listAllBoards.select()(state);
|
||||||
return isInCategory && isInSelectedBoard;
|
// const board = boards?.find((b) => b.board_id === board_id);
|
||||||
});
|
// if (!board) {
|
||||||
|
// // can't find the board in cache...
|
||||||
// get the board from the cache
|
// return;
|
||||||
const { data: boards } =
|
// }
|
||||||
boardsApi.endpoints.listAllBoards.select()(state);
|
// // if we haven't loaded one full page of images from this board, load more
|
||||||
const board = boards?.find((b) => b.board_id === board_id);
|
// if (
|
||||||
if (!board) {
|
// filteredImages.length < board.image_count &&
|
||||||
// can't find the board in cache...
|
// filteredImages.length < IMAGES_PER_PAGE
|
||||||
return;
|
// ) {
|
||||||
}
|
// dispatch(
|
||||||
|
// receivedPageOfImages({ categories, board_id, is_intermediate: false })
|
||||||
// if we haven't loaded one full page of images from this board, load more
|
// );
|
||||||
if (
|
// }
|
||||||
filteredImages.length < board.image_count &&
|
|
||||||
filteredImages.length < IMAGES_PER_PAGE
|
|
||||||
) {
|
|
||||||
dispatch(
|
|
||||||
receivedPageOfImages({ categories, board_id, is_intermediate: false })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
import { requestedBoardImagesDeletion } from 'features/gallery/store/actions';
|
|
||||||
import { startAppListening } from '..';
|
|
||||||
import {
|
|
||||||
imageSelected,
|
|
||||||
imagesRemoved,
|
|
||||||
selectImagesAll,
|
|
||||||
selectImagesById,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
import { requestedBoardImagesDeletion as requestedBoardAndImagesDeletion } from 'features/gallery/store/actions';
|
||||||
|
import {
|
||||||
|
imageSelected,
|
||||||
|
selectImagesById,
|
||||||
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
import { LIST_TAG, api } from 'services/api';
|
import { LIST_TAG, api } from 'services/api';
|
||||||
|
import { startAppListening } from '..';
|
||||||
import { boardsApi } from '../../../../../services/api/endpoints/boards';
|
import { boardsApi } from '../../../../../services/api/endpoints/boards';
|
||||||
|
|
||||||
export const addRequestedBoardImageDeletionListener = () => {
|
export const addRequestedBoardImageDeletionListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: requestedBoardImagesDeletion,
|
actionCreator: requestedBoardAndImagesDeletion,
|
||||||
effect: async (action, { dispatch, getState, condition }) => {
|
effect: async (action, { dispatch, getState, condition }) => {
|
||||||
const { board, imagesUsage } = action.payload;
|
const { board, imagesUsage } = action.payload;
|
||||||
|
|
||||||
@ -51,20 +49,12 @@ export const addRequestedBoardImageDeletionListener = () => {
|
|||||||
dispatch(nodeEditorReset());
|
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
|
// Delete from server
|
||||||
dispatch(boardsApi.endpoints.deleteBoardAndImages.initiate(board_id));
|
dispatch(boardsApi.endpoints.deleteBoardAndImages.initiate(board_id));
|
||||||
const result =
|
const result =
|
||||||
boardsApi.endpoints.deleteBoardAndImages.select(board_id)(state);
|
boardsApi.endpoints.deleteBoardAndImages.select(board_id)(state);
|
||||||
const { isSuccess } = result;
|
|
||||||
|
const { isSuccess, data } = result;
|
||||||
|
|
||||||
// Wait for successful deletion, then trigger boards to re-fetch
|
// Wait for successful deletion, then trigger boards to re-fetch
|
||||||
const wasBoardDeleted = await condition(() => !!isSuccess, 30000);
|
const wasBoardDeleted = await condition(() => !!isSuccess, 30000);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { canvasSavedToGallery } from 'features/canvas/store/actions';
|
|
||||||
import { startAppListening } from '..';
|
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { imageUploaded } from 'services/api/thunks/image';
|
import { canvasSavedToGallery } from 'features/canvas/store/actions';
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { imageUploaded } from 'services/api/thunks/image';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'canvasSavedToGalleryListener' });
|
const moduleLog = log.child({ namespace: 'canvasSavedToGalleryListener' });
|
||||||
|
|
||||||
@ -49,7 +49,11 @@ export const addCanvasSavedToGalleryListener = () => {
|
|||||||
uploadedImageAction.meta.requestId === imageUploadedRequest.requestId
|
uploadedImageAction.meta.requestId === imageUploadedRequest.requestId
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(imageUpserted(uploadedImageDTO));
|
imagesApi.util.upsertQueryData(
|
||||||
|
'getImageDTO',
|
||||||
|
uploadedImageDTO.image_name,
|
||||||
|
uploadedImageDTO
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { startAppListening } from '..';
|
|
||||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||||
import { Graph } from 'services/api/types';
|
|
||||||
import { sessionCreated } from 'services/api/thunks/session';
|
|
||||||
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
|
||||||
import { socketInvocationComplete } from 'services/events/actions';
|
|
||||||
import { isImageOutput } from 'services/api/guards';
|
|
||||||
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
import { controlNetProcessedImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||||
|
import { sessionReadyToInvoke } from 'features/system/store/actions';
|
||||||
|
import { isImageOutput } from 'services/api/guards';
|
||||||
|
import { imageDTOReceived } from 'services/api/thunks/image';
|
||||||
|
import { sessionCreated } from 'services/api/thunks/session';
|
||||||
|
import { Graph } from 'services/api/types';
|
||||||
|
import { socketInvocationComplete } from 'services/events/actions';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'controlNet' });
|
const moduleLog = log.child({ namespace: 'controlNet' });
|
||||||
|
|
||||||
@ -63,10 +63,8 @@ export const addControlNetImageProcessedListener = () => {
|
|||||||
|
|
||||||
// Wait for the ImageDTO to be received
|
// Wait for the ImageDTO to be received
|
||||||
const [imageMetadataReceivedAction] = await take(
|
const [imageMetadataReceivedAction] = await take(
|
||||||
(
|
(action): action is ReturnType<typeof imageDTOReceived.fulfilled> =>
|
||||||
action
|
imageDTOReceived.fulfilled.match(action) &&
|
||||||
): action is ReturnType<typeof imageMetadataReceived.fulfilled> =>
|
|
||||||
imageMetadataReceived.fulfilled.match(action) &&
|
|
||||||
action.payload.image_name === image_name
|
action.payload.image_name === image_name
|
||||||
);
|
);
|
||||||
const processedControlImage = imageMetadataReceivedAction.payload;
|
const processedControlImage = imageMetadataReceivedAction.payload;
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
|
||||||
import { startAppListening } from '..';
|
|
||||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
|
||||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'boards' });
|
|
||||||
|
|
||||||
export const addImageAddedToBoardFulfilledListener = () => {
|
|
||||||
startAppListening({
|
|
||||||
matcher: boardImagesApi.endpoints.addImageToBoard.matchFulfilled,
|
|
||||||
effect: (action, { getState, dispatch }) => {
|
|
||||||
const { board_id, image_name } = action.meta.arg.originalArgs;
|
|
||||||
|
|
||||||
moduleLog.debug(
|
|
||||||
{ data: { board_id, image_name } },
|
|
||||||
'Image added to board'
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imageMetadataReceived({
|
|
||||||
image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addImageAddedToBoardRejectedListener = () => {
|
|
||||||
startAppListening({
|
|
||||||
matcher: boardImagesApi.endpoints.addImageToBoard.matchRejected,
|
|
||||||
effect: (action, { getState, dispatch }) => {
|
|
||||||
const { board_id, image_name } = action.meta.arg.originalArgs;
|
|
||||||
|
|
||||||
moduleLog.debug(
|
|
||||||
{ data: { board_id, image_name } },
|
|
||||||
'Problem adding image to board'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,13 +1,13 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { imageDTOReceived, imageUpdated } from 'services/api/thunks/image';
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
import { imageMetadataReceived, imageUpdated } from 'services/api/thunks/image';
|
|
||||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'image' });
|
const moduleLog = log.child({ namespace: 'image' });
|
||||||
|
|
||||||
export const addImageMetadataReceivedFulfilledListener = () => {
|
export const addImageDTOReceivedFulfilledListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imageMetadataReceived.fulfilled,
|
actionCreator: imageDTOReceived.fulfilled,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
const image = action.payload;
|
const image = action.payload;
|
||||||
|
|
||||||
@ -33,14 +33,14 @@ export const addImageMetadataReceivedFulfilledListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
moduleLog.debug({ data: { image } }, 'Image metadata received');
|
moduleLog.debug({ data: { image } }, 'Image metadata received');
|
||||||
dispatch(imageUpserted(image));
|
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addImageMetadataReceivedRejectedListener = () => {
|
export const addImageDTOReceivedRejectedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imageMetadataReceived.rejected,
|
actionCreator: imageDTOReceived.rejected,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
moduleLog.debug(
|
moduleLog.debug(
|
||||||
{ data: { image: action.meta.arg } },
|
{ data: { image: action.meta.arg } },
|
@ -1,11 +1,8 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||||
import {
|
import { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
|
||||||
imageRemoved,
|
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||||
imageSelected,
|
|
||||||
selectFilteredImages,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import {
|
import {
|
||||||
imageDeletionConfirmed,
|
imageDeletionConfirmed,
|
||||||
isModalOpenChanged,
|
isModalOpenChanged,
|
||||||
@ -80,9 +77,6 @@ export const addRequestedImageDeletionListener = () => {
|
|||||||
dispatch(nodeEditorReset());
|
dispatch(nodeEditorReset());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preemptively remove from gallery
|
|
||||||
dispatch(imageRemoved(image_name));
|
|
||||||
|
|
||||||
// Delete from server
|
// Delete from server
|
||||||
const { requestId } = dispatch(imageDeleted({ image_name }));
|
const { requestId } = dispatch(imageDeleted({ image_name }));
|
||||||
|
|
||||||
@ -91,7 +85,7 @@ export const addRequestedImageDeletionListener = () => {
|
|||||||
(action): action is ReturnType<typeof imageDeleted.fulfilled> =>
|
(action): action is ReturnType<typeof imageDeleted.fulfilled> =>
|
||||||
imageDeleted.fulfilled.match(action) &&
|
imageDeleted.fulfilled.match(action) &&
|
||||||
action.meta.requestId === requestId,
|
action.meta.requestId === requestId,
|
||||||
30000
|
30_000
|
||||||
);
|
);
|
||||||
|
|
||||||
if (wasImageDeleted) {
|
if (wasImageDeleted) {
|
||||||
|
@ -21,57 +21,66 @@ import { startAppListening } from '../';
|
|||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'dnd' });
|
const moduleLog = log.child({ namespace: 'dnd' });
|
||||||
|
|
||||||
export const imageDropped = createAction<{
|
export const dndDropped = createAction<{
|
||||||
overData: TypesafeDroppableData;
|
overData: TypesafeDroppableData;
|
||||||
activeData: TypesafeDraggableData;
|
activeData: TypesafeDraggableData;
|
||||||
}>('dnd/imageDropped');
|
}>('dnd/dndDropped');
|
||||||
|
|
||||||
export const addImageDroppedListener = () => {
|
export const addImageDroppedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imageDropped,
|
actionCreator: dndDropped,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
const { activeData, overData } = action.payload;
|
const { activeData, overData } = action.payload;
|
||||||
const { actionType } = overData;
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { activeData, overData } },
|
||||||
|
'Image or selection dropped'
|
||||||
|
);
|
||||||
|
|
||||||
// set current image
|
// set current image
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_CURRENT_IMAGE' &&
|
overData.actionType === 'SET_CURRENT_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(imageSelected(activeData.payload.imageDTO.image_name));
|
dispatch(imageSelected(activeData.payload.imageDTO.image_name));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set initial image
|
// set initial image
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_INITIAL_IMAGE' &&
|
overData.actionType === 'SET_INITIAL_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(initialImageChanged(activeData.payload.imageDTO));
|
dispatch(initialImageChanged(activeData.payload.imageDTO));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add image to batch
|
// add image to batch
|
||||||
if (
|
if (
|
||||||
actionType === 'ADD_TO_BATCH' &&
|
overData.actionType === 'ADD_TO_BATCH' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(imageAddedToBatch(activeData.payload.imageDTO.image_name));
|
dispatch(imageAddedToBatch(activeData.payload.imageDTO.image_name));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add multiple images to batch
|
// add multiple images to batch
|
||||||
if (
|
if (
|
||||||
actionType === 'ADD_TO_BATCH' &&
|
overData.actionType === 'ADD_TO_BATCH' &&
|
||||||
activeData.payloadType === 'GALLERY_SELECTION'
|
activeData.payloadType === 'IMAGE_NAMES'
|
||||||
) {
|
) {
|
||||||
dispatch(imagesAddedToBatch(state.gallery.selection));
|
dispatch(imagesAddedToBatch(activeData.payload.image_names));
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set control image
|
// set control image
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_CONTROLNET_IMAGE' &&
|
overData.actionType === 'SET_CONTROLNET_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
@ -82,20 +91,22 @@ export const addImageDroppedListener = () => {
|
|||||||
controlNetId,
|
controlNetId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set canvas image
|
// set canvas image
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_CANVAS_INITIAL_IMAGE' &&
|
overData.actionType === 'SET_CANVAS_INITIAL_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
dispatch(setInitialCanvasImage(activeData.payload.imageDTO));
|
dispatch(setInitialCanvasImage(activeData.payload.imageDTO));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set nodes image
|
// set nodes image
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_NODES_IMAGE' &&
|
overData.actionType === 'SET_NODES_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
@ -107,11 +118,12 @@ export const addImageDroppedListener = () => {
|
|||||||
value: activeData.payload.imageDTO,
|
value: activeData.payload.imageDTO,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set multiple nodes images (single image handler)
|
// set multiple nodes images (single image handler)
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_MULTI_NODES_IMAGE' &&
|
overData.actionType === 'SET_MULTI_NODES_IMAGE' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO
|
activeData.payload.imageDTO
|
||||||
) {
|
) {
|
||||||
@ -123,43 +135,30 @@ export const addImageDroppedListener = () => {
|
|||||||
value: [activeData.payload.imageDTO],
|
value: [activeData.payload.imageDTO],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set multiple nodes images (multiple images handler)
|
// set multiple nodes images (multiple images handler)
|
||||||
if (
|
if (
|
||||||
actionType === 'SET_MULTI_NODES_IMAGE' &&
|
overData.actionType === 'SET_MULTI_NODES_IMAGE' &&
|
||||||
activeData.payloadType === 'GALLERY_SELECTION'
|
activeData.payloadType === 'IMAGE_NAMES'
|
||||||
) {
|
) {
|
||||||
const { fieldName, nodeId } = overData.context;
|
const { fieldName, nodeId } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
imageCollectionFieldValueChanged({
|
imageCollectionFieldValueChanged({
|
||||||
nodeId,
|
nodeId,
|
||||||
fieldName,
|
fieldName,
|
||||||
value: state.gallery.selection.map((image_name) => ({
|
value: activeData.payload.image_names.map((image_name) => ({
|
||||||
image_name,
|
image_name,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove image from board
|
|
||||||
// TODO: remove board_id from `removeImageFromBoard()` endpoint
|
|
||||||
// TODO: handle multiple images
|
|
||||||
// if (
|
|
||||||
// actionType === 'MOVE_BOARD' &&
|
|
||||||
// activeData.payloadType === 'IMAGE_DTO' &&
|
|
||||||
// activeData.payload.imageDTO &&
|
|
||||||
// overData.boardId !== null
|
|
||||||
// ) {
|
|
||||||
// const { image_name } = activeData.payload.imageDTO;
|
|
||||||
// dispatch(
|
|
||||||
// boardImagesApi.endpoints.removeImageFromBoard.initiate({ image_name })
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// add image to board
|
// add image to board
|
||||||
if (
|
if (
|
||||||
actionType === 'MOVE_BOARD' &&
|
overData.actionType === 'MOVE_BOARD' &&
|
||||||
activeData.payloadType === 'IMAGE_DTO' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
activeData.payload.imageDTO &&
|
activeData.payload.imageDTO &&
|
||||||
overData.context.boardId
|
overData.context.boardId
|
||||||
@ -167,22 +166,89 @@ export const addImageDroppedListener = () => {
|
|||||||
const { image_name } = activeData.payload.imageDTO;
|
const { image_name } = activeData.payload.imageDTO;
|
||||||
const { boardId } = overData.context;
|
const { boardId } = overData.context;
|
||||||
dispatch(
|
dispatch(
|
||||||
boardImagesApi.endpoints.addImageToBoard.initiate({
|
boardImagesApi.endpoints.addBoardImage.initiate({
|
||||||
image_name,
|
image_name,
|
||||||
board_id: boardId,
|
board_id: boardId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add multiple images to board
|
// remove image from board
|
||||||
// TODO: add endpoint
|
if (
|
||||||
// if (
|
overData.actionType === 'MOVE_BOARD' &&
|
||||||
// actionType === 'ADD_TO_BATCH' &&
|
activeData.payloadType === 'IMAGE_DTO' &&
|
||||||
// activeData.payloadType === 'IMAGE_NAMES' &&
|
activeData.payload.imageDTO &&
|
||||||
// activeData.payload.imageDTONames
|
overData.context.boardId === null
|
||||||
// ) {
|
) {
|
||||||
// dispatch(boardImagesApi.endpoints.addImagesToBoard.intiate({}));
|
const { image_name } = activeData.payload.imageDTO;
|
||||||
// }
|
dispatch(
|
||||||
|
boardImagesApi.endpoints.deleteBoardImage.initiate({ image_name })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add gallery selection to board
|
||||||
|
if (
|
||||||
|
overData.actionType === 'MOVE_BOARD' &&
|
||||||
|
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||||
|
overData.context.boardId
|
||||||
|
) {
|
||||||
|
console.log('adding gallery selection to board');
|
||||||
|
const board_id = overData.context.boardId;
|
||||||
|
dispatch(
|
||||||
|
boardImagesApi.endpoints.addManyBoardImages.initiate({
|
||||||
|
board_id,
|
||||||
|
image_names: activeData.payload.image_names,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove gallery selection from board
|
||||||
|
if (
|
||||||
|
overData.actionType === 'MOVE_BOARD' &&
|
||||||
|
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||||
|
overData.context.boardId === null
|
||||||
|
) {
|
||||||
|
console.log('removing gallery selection to board');
|
||||||
|
dispatch(
|
||||||
|
boardImagesApi.endpoints.deleteManyBoardImages.initiate({
|
||||||
|
image_names: activeData.payload.image_names,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add batch selection to board
|
||||||
|
if (
|
||||||
|
overData.actionType === 'MOVE_BOARD' &&
|
||||||
|
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||||
|
overData.context.boardId
|
||||||
|
) {
|
||||||
|
const board_id = overData.context.boardId;
|
||||||
|
dispatch(
|
||||||
|
boardImagesApi.endpoints.addManyBoardImages.initiate({
|
||||||
|
board_id,
|
||||||
|
image_names: activeData.payload.image_names,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove batch selection from board
|
||||||
|
if (
|
||||||
|
overData.actionType === 'MOVE_BOARD' &&
|
||||||
|
activeData.payloadType === 'IMAGE_NAMES' &&
|
||||||
|
overData.context.boardId === null
|
||||||
|
) {
|
||||||
|
dispatch(
|
||||||
|
boardImagesApi.endpoints.deleteManyBoardImages.initiate({
|
||||||
|
image_names: activeData.payload.image_names,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
|
||||||
import { startAppListening } from '..';
|
|
||||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
|
||||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'boards' });
|
|
||||||
|
|
||||||
export const addImageRemovedFromBoardFulfilledListener = () => {
|
|
||||||
startAppListening({
|
|
||||||
matcher: boardImagesApi.endpoints.removeImageFromBoard.matchFulfilled,
|
|
||||||
effect: (action, { getState, dispatch }) => {
|
|
||||||
const { board_id, image_name } = action.meta.arg.originalArgs;
|
|
||||||
|
|
||||||
moduleLog.debug(
|
|
||||||
{ data: { board_id, image_name } },
|
|
||||||
'Image added to board'
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imageMetadataReceived({
|
|
||||||
image_name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addImageRemovedFromBoardRejectedListener = () => {
|
|
||||||
startAppListening({
|
|
||||||
matcher: boardImagesApi.endpoints.removeImageFromBoard.matchRejected,
|
|
||||||
effect: (action, { getState, dispatch }) => {
|
|
||||||
const { board_id, image_name } = action.meta.arg.originalArgs;
|
|
||||||
|
|
||||||
moduleLog.debug(
|
|
||||||
{ data: { board_id, image_name } },
|
|
||||||
'Problem adding image to board'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,13 +1,13 @@
|
|||||||
import { startAppListening } from '..';
|
|
||||||
import { imageUploaded } from 'services/api/thunks/image';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
import { imageAddedToBatch } from 'features/batch/store/batchSlice';
|
||||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||||
import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice';
|
import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
|
||||||
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { imageAddedToBatch } from 'features/batch/store/batchSlice';
|
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||||
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { imageUploaded } from 'services/api/thunks/image';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'image' });
|
const moduleLog = log.child({ namespace: 'image' });
|
||||||
|
|
||||||
@ -24,7 +24,8 @@ export const addImageUploadedFulfilledListener = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(imageUpserted(image));
|
// update RTK query cache
|
||||||
|
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image);
|
||||||
|
|
||||||
const { postUploadAction } = action.meta.arg;
|
const { postUploadAction } = action.meta.arg;
|
||||||
|
|
||||||
@ -84,8 +85,8 @@ export const addImageUploadedRejectedListener = () => {
|
|||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: imageUploaded.rejected,
|
actionCreator: imageUploaded.rejected,
|
||||||
effect: (action, { dispatch }) => {
|
effect: (action, { dispatch }) => {
|
||||||
const { formData, ...rest } = action.meta.arg;
|
const { file, ...rest } = action.meta.arg;
|
||||||
const sanitizedData = { arg: { ...rest, formData: { file: '<Blob>' } } };
|
const sanitizedData = { arg: { ...rest, file: '<Blob>' } };
|
||||||
moduleLog.error({ data: sanitizedData }, 'Image upload failed');
|
moduleLog.error({ data: sanitizedData }, 'Image upload failed');
|
||||||
dispatch(
|
dispatch(
|
||||||
addToast({
|
addToast({
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
|
||||||
import { startAppListening } from '..';
|
|
||||||
import { imageUrlsReceived } from 'services/api/thunks/image';
|
|
||||||
import { imageUpdatedOne } from 'features/gallery/store/gallerySlice';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'image' });
|
|
||||||
|
|
||||||
export const addImageUrlsReceivedFulfilledListener = () => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: imageUrlsReceived.fulfilled,
|
|
||||||
effect: (action, { getState, dispatch }) => {
|
|
||||||
const image = action.payload;
|
|
||||||
moduleLog.debug({ data: { image } }, 'Image URLs received');
|
|
||||||
|
|
||||||
const { image_name, image_url, thumbnail_url } = image;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imageUpdatedOne({
|
|
||||||
id: image_name,
|
|
||||||
changes: { image_url, thumbnail_url },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addImageUrlsReceivedRejectedListener = () => {
|
|
||||||
startAppListening({
|
|
||||||
actionCreator: imageUrlsReceived.rejected,
|
|
||||||
effect: (action, { getState, dispatch }) => {
|
|
||||||
moduleLog.debug(
|
|
||||||
{ data: { image: action.meta.arg } },
|
|
||||||
'Problem getting image URLs'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
@ -0,0 +1,38 @@
|
|||||||
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { imagesLoaded } from 'services/api/thunks/image';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
|
const moduleLog = log.child({ namespace: 'gallery' });
|
||||||
|
|
||||||
|
export const addImagesLoadedListener = () => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: imagesLoaded.fulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { items } = action.payload;
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { payload: action.payload } },
|
||||||
|
`Loaded ${items.length} images`
|
||||||
|
);
|
||||||
|
|
||||||
|
items.forEach((image) => {
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: imagesLoaded.rejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
if (action.payload) {
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { error: serializeError(action.payload) } },
|
||||||
|
'Problem loading images'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,38 @@
|
|||||||
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { serializeError } from 'serialize-error';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { receivedListOfImages } from 'services/api/thunks/image';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
|
const moduleLog = log.child({ namespace: 'gallery' });
|
||||||
|
|
||||||
|
export const addReceivedListOfImagesListener = () => {
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: receivedListOfImages.fulfilled,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
const { image_dtos } = action.payload;
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { payload: action.payload } },
|
||||||
|
`Received ${image_dtos.length} images`
|
||||||
|
);
|
||||||
|
|
||||||
|
image_dtos.forEach((image) => {
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
startAppListening({
|
||||||
|
actionCreator: receivedListOfImages.rejected,
|
||||||
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
if (action.payload) {
|
||||||
|
moduleLog.debug(
|
||||||
|
{ data: { error: serializeError(action.payload) } },
|
||||||
|
'Problem receiving images'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
@ -1,12 +1,12 @@
|
|||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { startAppListening } from '..';
|
|
||||||
import { serializeError } from 'serialize-error';
|
import { serializeError } from 'serialize-error';
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
|
||||||
import { imagesApi } from 'services/api/endpoints/images';
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'gallery' });
|
const moduleLog = log.child({ namespace: 'gallery' });
|
||||||
|
|
||||||
export const addReceivedPageOfImagesFulfilledListener = () => {
|
export const addReceivedPageOfImagesListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: receivedPageOfImages.fulfilled,
|
actionCreator: receivedPageOfImages.fulfilled,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
@ -16,6 +16,8 @@ export const addReceivedPageOfImagesFulfilledListener = () => {
|
|||||||
`Received ${items.length} images`
|
`Received ${items.length} images`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// inject the received images into the RTK Query cache so consumers of the useGetImageDTOQuery
|
||||||
|
// hook can get their data from the cache instead of fetching the data again
|
||||||
items.forEach((image) => {
|
items.forEach((image) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
||||||
@ -23,9 +25,7 @@ export const addReceivedPageOfImagesFulfilledListener = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export const addReceivedPageOfImagesRejectedListener = () => {
|
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: receivedPageOfImages.rejected,
|
actionCreator: receivedPageOfImages.rejected,
|
||||||
effect: (action, { getState, dispatch }) => {
|
effect: (action, { getState, dispatch }) => {
|
||||||
|
@ -1,19 +1,38 @@
|
|||||||
import { startAppListening } from '..';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import {
|
import { imagesAddedToBatch } from 'features/batch/store/batchSlice';
|
||||||
imagesAddedToBatch,
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
selectionAddedToBatch,
|
import { receivedListOfImages } from 'services/api/thunks/image';
|
||||||
} from 'features/batch/store/batchSlice';
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'batch' });
|
const moduleLog = log.child({ namespace: 'batch' });
|
||||||
|
|
||||||
|
export const selectionAddedToBatch = createAction<{ images_names: string[] }>(
|
||||||
|
'batch/selectionAddedToBatch'
|
||||||
|
);
|
||||||
|
|
||||||
export const addSelectionAddedToBatchListener = () => {
|
export const addSelectionAddedToBatchListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: selectionAddedToBatch,
|
actionCreator: selectionAddedToBatch,
|
||||||
effect: (action, { dispatch, getState }) => {
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
const { selection } = getState().gallery;
|
const { requestId } = dispatch(
|
||||||
|
receivedListOfImages(action.payload.images_names)
|
||||||
|
);
|
||||||
|
|
||||||
dispatch(imagesAddedToBatch(selection));
|
const [{ payload }] = await take(
|
||||||
|
(action): action is ReturnType<typeof receivedListOfImages.fulfilled> =>
|
||||||
|
action.meta.requestId === requestId
|
||||||
|
);
|
||||||
|
|
||||||
|
moduleLog.debug({ data: { payload } }, 'receivedListOfImages');
|
||||||
|
|
||||||
|
dispatch(imagesAddedToBatch(payload.image_dtos));
|
||||||
|
|
||||||
|
payload.image_dtos.forEach((image) => {
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
|
||||||
import { startAppListening } from '../..';
|
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
|
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
|
||||||
|
import { progressImageSet } from 'features/system/store/systemSlice';
|
||||||
|
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { isImageOutput } from 'services/api/guards';
|
||||||
|
import { imageDTOReceived } from 'services/api/thunks/image';
|
||||||
|
import { sessionCanceled } from 'services/api/thunks/session';
|
||||||
import {
|
import {
|
||||||
appSocketInvocationComplete,
|
appSocketInvocationComplete,
|
||||||
socketInvocationComplete,
|
socketInvocationComplete,
|
||||||
} from 'services/events/actions';
|
} from 'services/events/actions';
|
||||||
import { imageMetadataReceived } from 'services/api/thunks/image';
|
import { startAppListening } from '../..';
|
||||||
import { sessionCanceled } from 'services/api/thunks/session';
|
|
||||||
import { isImageOutput } from 'services/api/guards';
|
|
||||||
import { progressImageSet } from 'features/system/store/systemSlice';
|
|
||||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'socketio' });
|
const moduleLog = log.child({ namespace: 'socketio' });
|
||||||
const nodeDenylist = ['dataURL_image'];
|
const nodeDenylist = ['dataURL_image'];
|
||||||
@ -41,14 +42,16 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
const { image_name } = result.image;
|
const { image_name } = result.image;
|
||||||
|
|
||||||
// Get its metadata
|
// Get its metadata
|
||||||
dispatch(
|
const { requestId } = dispatch(
|
||||||
imageMetadataReceived({
|
imageDTOReceived({
|
||||||
image_name,
|
image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const [{ payload: imageDTO }] = await take(
|
const [{ payload: imageDTO }] = await take(
|
||||||
imageMetadataReceived.fulfilled.match
|
(action): action is ReturnType<typeof imageDTOReceived.fulfilled> =>
|
||||||
|
imageDTOReceived.fulfilled.match(action) &&
|
||||||
|
action.meta.requestId === requestId
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle canvas image
|
// Handle canvas image
|
||||||
@ -59,13 +62,33 @@ export const addInvocationCompleteEventListener = () => {
|
|||||||
dispatch(addImageToStagingArea(imageDTO));
|
dispatch(addImageToStagingArea(imageDTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the RTK Query cache
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.upsertQueryData(
|
||||||
|
'getImageDTO',
|
||||||
|
imageDTO.image_name,
|
||||||
|
imageDTO
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (boardIdToAddTo && !imageDTO.is_intermediate) {
|
if (boardIdToAddTo && !imageDTO.is_intermediate) {
|
||||||
dispatch(
|
dispatch(
|
||||||
boardImagesApi.endpoints.addImageToBoard.initiate({
|
boardImagesApi.endpoints.addBoardImage.initiate({
|
||||||
board_id: boardIdToAddTo,
|
board_id: boardIdToAddTo,
|
||||||
image_name,
|
image_name,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Set the board_id on the image in the RTK Query cache
|
||||||
|
dispatch(
|
||||||
|
imagesApi.util.updateQueryData(
|
||||||
|
'getImageDTO',
|
||||||
|
imageDTO.image_name,
|
||||||
|
(draft) => {
|
||||||
|
Object.assign(draft, { board_id: boardIdToAddTo });
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(progressImageSet(null));
|
dispatch(progressImageSet(null));
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
|
||||||
import { startAppListening } from '..';
|
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { imageUpdated } from 'services/api/thunks/image';
|
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
||||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
|
import { imagesApi } from 'services/api/endpoints/images';
|
||||||
|
import { imageUpdated } from 'services/api/thunks/image';
|
||||||
|
import { startAppListening } from '..';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'canvas' });
|
const moduleLog = log.child({ namespace: 'canvas' });
|
||||||
|
|
||||||
@ -43,7 +43,10 @@ export const addStagingAreaImageSavedListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (imageUpdated.fulfilled.match(imageUpdatedAction)) {
|
if (imageUpdated.fulfilled.match(imageUpdatedAction)) {
|
||||||
dispatch(imageUpserted(imageUpdatedAction.payload));
|
// update cache
|
||||||
|
imagesApi.util.updateQueryData('getImageDTO', imageName, (draft) => {
|
||||||
|
Object.assign(draft, { is_intermediate: false });
|
||||||
|
});
|
||||||
dispatch(addToast({ title: 'Image Saved', status: 'success' }));
|
dispatch(addToast({ title: 'Image Saved', status: 'success' }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -96,10 +96,26 @@ export const store = configureStore({
|
|||||||
.concat(dynamicMiddlewares)
|
.concat(dynamicMiddlewares)
|
||||||
.prepend(listenerMiddleware.middleware),
|
.prepend(listenerMiddleware.middleware),
|
||||||
devTools: {
|
devTools: {
|
||||||
actionsDenylist,
|
|
||||||
actionSanitizer,
|
actionSanitizer,
|
||||||
stateSanitizer,
|
stateSanitizer,
|
||||||
trace: true,
|
trace: true,
|
||||||
|
predicate: (state, action) => {
|
||||||
|
// TODO: hook up to the log level param in system slice
|
||||||
|
// manually type state, cannot type the arg
|
||||||
|
// const typedState = state as ReturnType<typeof rootReducer>;
|
||||||
|
|
||||||
|
// if (action.type.startsWith('api/')) {
|
||||||
|
// // don't log api actions, with manual cache updates they are extremely noisy
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (actionsDenylist.includes(action.type)) {
|
||||||
|
// don't log other noisy actions
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,30 +6,24 @@ import {
|
|||||||
useColorMode,
|
useColorMode,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useCombinedRefs } from '@dnd-kit/utilities';
|
import {
|
||||||
|
TypesafeDraggableData,
|
||||||
|
TypesafeDroppableData,
|
||||||
|
} from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import {
|
import {
|
||||||
IAILoadingImageFallback,
|
IAILoadingImageFallback,
|
||||||
IAINoContentFallback,
|
IAINoContentFallback,
|
||||||
} from 'common/components/IAIImageFallback';
|
} from 'common/components/IAIImageFallback';
|
||||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
|
||||||
import { MouseEvent, ReactElement, SyntheticEvent } from 'react';
|
|
||||||
import { memo, useRef } from 'react';
|
|
||||||
import { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
|
|
||||||
import { ImageDTO } from 'services/api/types';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import IAIDropOverlay from './IAIDropOverlay';
|
|
||||||
import { PostUploadAction } from 'services/api/thunks/image';
|
|
||||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||||
|
import { MouseEvent, ReactElement, SyntheticEvent, memo } from 'react';
|
||||||
|
import { FaImage, FaUndo, FaUpload } from 'react-icons/fa';
|
||||||
|
import { PostUploadAction } from 'services/api/thunks/image';
|
||||||
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
import {
|
import IAIDraggable from './IAIDraggable';
|
||||||
TypesafeDraggableData,
|
import IAIDroppable from './IAIDroppable';
|
||||||
TypesafeDroppableData,
|
|
||||||
isValidDrop,
|
|
||||||
useDraggable,
|
|
||||||
useDroppable,
|
|
||||||
} from 'app/components/ImageDnd/typesafeDnd';
|
|
||||||
|
|
||||||
type IAIDndImageProps = {
|
type IAIDndImageProps = {
|
||||||
imageDTO: ImageDTO | undefined;
|
imageDTO: ImageDTO | undefined;
|
||||||
@ -83,28 +77,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
const dndId = useRef(uuidv4());
|
|
||||||
|
|
||||||
const {
|
|
||||||
attributes,
|
|
||||||
listeners,
|
|
||||||
setNodeRef: setDraggableRef,
|
|
||||||
isDragging,
|
|
||||||
active,
|
|
||||||
} = useDraggable({
|
|
||||||
id: dndId.current,
|
|
||||||
disabled: isDragDisabled || !imageDTO,
|
|
||||||
data: draggableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { isOver, setNodeRef: setDroppableRef } = useDroppable({
|
|
||||||
id: dndId.current,
|
|
||||||
disabled: isDropDisabled,
|
|
||||||
data: droppableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const setDndRef = useCombinedRefs(setDroppableRef, setDraggableRef);
|
|
||||||
|
|
||||||
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
const { getUploadButtonProps, getUploadInputProps } = useImageUploadButton({
|
||||||
postUploadAction,
|
postUploadAction,
|
||||||
isDisabled: isUploadDisabled,
|
isDisabled: isUploadDisabled,
|
||||||
@ -139,9 +111,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
cursor: isDragDisabled || !imageDTO ? 'default' : 'pointer',
|
cursor: isDragDisabled || !imageDTO ? 'default' : 'pointer',
|
||||||
}}
|
}}
|
||||||
{...attributes}
|
|
||||||
{...listeners}
|
|
||||||
ref={setDndRef}
|
|
||||||
>
|
>
|
||||||
{imageDTO && (
|
{imageDTO && (
|
||||||
<Flex
|
<Flex
|
||||||
@ -154,7 +123,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
onClick={onClick}
|
|
||||||
src={thumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
|
src={thumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
|
||||||
fallbackStrategy="beforeLoadOrError"
|
fallbackStrategy="beforeLoadOrError"
|
||||||
fallback={<IAILoadingImageFallback image={imageDTO} />}
|
fallback={<IAILoadingImageFallback image={imageDTO} />}
|
||||||
@ -171,30 +139,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{withMetadataOverlay && <ImageMetadataOverlay image={imageDTO} />}
|
{withMetadataOverlay && <ImageMetadataOverlay image={imageDTO} />}
|
||||||
{onClickReset && withResetIcon && (
|
|
||||||
<IAIIconButton
|
|
||||||
onClick={onClickReset}
|
|
||||||
aria-label={resetTooltip}
|
|
||||||
tooltip={resetTooltip}
|
|
||||||
icon={resetIcon}
|
|
||||||
size="sm"
|
|
||||||
variant="link"
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 1,
|
|
||||||
insetInlineEnd: 1,
|
|
||||||
p: 0,
|
|
||||||
minW: 0,
|
|
||||||
svg: {
|
|
||||||
transitionProperty: 'common',
|
|
||||||
transitionDuration: 'normal',
|
|
||||||
fill: 'base.100',
|
|
||||||
_hover: { fill: 'base.50' },
|
|
||||||
filter: resetIconShadow,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{!imageDTO && !isUploadDisabled && (
|
{!imageDTO && !isUploadDisabled && (
|
||||||
@ -225,11 +169,42 @@ const IAIDndImage = (props: IAIDndImageProps) => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!imageDTO && isUploadDisabled && noContentFallback}
|
{!imageDTO && isUploadDisabled && noContentFallback}
|
||||||
<AnimatePresence>
|
<IAIDroppable
|
||||||
{isValidDrop(droppableData, active) && !isDragging && (
|
data={droppableData}
|
||||||
<IAIDropOverlay isOver={isOver} label={dropLabel} />
|
disabled={isDropDisabled}
|
||||||
)}
|
dropLabel={dropLabel}
|
||||||
</AnimatePresence>
|
/>
|
||||||
|
{imageDTO && (
|
||||||
|
<IAIDraggable
|
||||||
|
data={draggableData}
|
||||||
|
disabled={isDragDisabled || !imageDTO}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{onClickReset && withResetIcon && imageDTO && (
|
||||||
|
<IAIIconButton
|
||||||
|
onClick={onClickReset}
|
||||||
|
aria-label={resetTooltip}
|
||||||
|
tooltip={resetTooltip}
|
||||||
|
icon={resetIcon}
|
||||||
|
size="sm"
|
||||||
|
variant="link"
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 1,
|
||||||
|
insetInlineEnd: 1,
|
||||||
|
p: 0,
|
||||||
|
minW: 0,
|
||||||
|
svg: {
|
||||||
|
transitionProperty: 'common',
|
||||||
|
transitionDuration: 'normal',
|
||||||
|
fill: 'base.100',
|
||||||
|
_hover: { fill: 'base.50' },
|
||||||
|
filter: resetIconShadow,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
40
invokeai/frontend/web/src/common/components/IAIDraggable.tsx
Normal file
40
invokeai/frontend/web/src/common/components/IAIDraggable.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
TypesafeDraggableData,
|
||||||
|
useDraggable,
|
||||||
|
} from 'app/components/ImageDnd/typesafeDnd';
|
||||||
|
import { MouseEvent, memo, useRef } from 'react';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
type IAIDraggableProps = {
|
||||||
|
disabled?: boolean;
|
||||||
|
data?: TypesafeDraggableData;
|
||||||
|
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAIDraggable = (props: IAIDraggableProps) => {
|
||||||
|
const { data, disabled, onClick } = props;
|
||||||
|
const dndId = useRef(uuidv4());
|
||||||
|
|
||||||
|
const { attributes, listeners, setNodeRef } = useDraggable({
|
||||||
|
id: dndId.current,
|
||||||
|
disabled,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
onClick={onClick}
|
||||||
|
ref={setNodeRef}
|
||||||
|
position="absolute"
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
top={0}
|
||||||
|
insetInlineStart={0}
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(IAIDraggable);
|
47
invokeai/frontend/web/src/common/components/IAIDroppable.tsx
Normal file
47
invokeai/frontend/web/src/common/components/IAIDroppable.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
TypesafeDroppableData,
|
||||||
|
isValidDrop,
|
||||||
|
useDroppable,
|
||||||
|
} from 'app/components/ImageDnd/typesafeDnd';
|
||||||
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
import { memo, useRef } from 'react';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import IAIDropOverlay from './IAIDropOverlay';
|
||||||
|
|
||||||
|
type IAIDroppableProps = {
|
||||||
|
dropLabel?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
data?: TypesafeDroppableData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const IAIDroppable = (props: IAIDroppableProps) => {
|
||||||
|
const { dropLabel, data, disabled } = props;
|
||||||
|
const dndId = useRef(uuidv4());
|
||||||
|
|
||||||
|
const { isOver, setNodeRef, active } = useDroppable({
|
||||||
|
id: dndId.current,
|
||||||
|
disabled,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
ref={setNodeRef}
|
||||||
|
position="absolute"
|
||||||
|
top={0}
|
||||||
|
insetInlineStart={0}
|
||||||
|
w="full"
|
||||||
|
h="full"
|
||||||
|
pointerEvents="none"
|
||||||
|
>
|
||||||
|
<AnimatePresence>
|
||||||
|
{isValidDrop(data, active) && (
|
||||||
|
<IAIDropOverlay isOver={isOver} label={dropLabel} />
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(IAIDroppable);
|
@ -0,0 +1,42 @@
|
|||||||
|
import { Box, Flex, Icon } from '@chakra-ui/react';
|
||||||
|
import { FaExclamation } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const IAIErrorLoadingImageFallback = () => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
height: 'full',
|
||||||
|
width: 'full',
|
||||||
|
'::before': {
|
||||||
|
content: "''",
|
||||||
|
display: 'block',
|
||||||
|
pt: '100%',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
height: 'full',
|
||||||
|
width: 'full',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderRadius: 'base',
|
||||||
|
bg: 'base.100',
|
||||||
|
color: 'base.500',
|
||||||
|
_dark: {
|
||||||
|
color: 'base.700',
|
||||||
|
bg: 'base.850',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon as={FaExclamation} boxSize={16} opacity={0.7} />
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAIErrorLoadingImageFallback;
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Box, Skeleton } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const IAIFillSkeleton = () => {
|
||||||
|
return (
|
||||||
|
<Skeleton
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
height: 'full',
|
||||||
|
width: 'full',
|
||||||
|
'::before': {
|
||||||
|
content: "''",
|
||||||
|
display: 'block',
|
||||||
|
pt: '100%',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
insetInlineStart: 0,
|
||||||
|
height: 'full',
|
||||||
|
width: 'full',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IAIFillSkeleton;
|
@ -1,18 +1,20 @@
|
|||||||
import { Box, Icon, Skeleton } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
|
import IAIErrorLoadingImageFallback from 'common/components/IAIErrorLoadingImageFallback';
|
||||||
|
import IAIFillSkeleton from 'common/components/IAIFillSkeleton';
|
||||||
import {
|
import {
|
||||||
batchImageRangeEndSelected,
|
batchImageRangeEndSelected,
|
||||||
batchImageSelected,
|
batchImageSelected,
|
||||||
batchImageSelectionToggled,
|
batchImageSelectionToggled,
|
||||||
imageRemovedFromBatch,
|
imageRemovedFromBatch,
|
||||||
} from 'features/batch/store/batchSlice';
|
} from 'features/batch/store/batchSlice';
|
||||||
|
import ImageContextMenu from 'features/gallery/components/ImageContextMenu';
|
||||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||||
import { FaExclamationCircle } from 'react-icons/fa';
|
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
|
||||||
const makeSelector = (image_name: string) =>
|
const makeSelector = (image_name: string) =>
|
||||||
@ -20,6 +22,7 @@ const makeSelector = (image_name: string) =>
|
|||||||
[stateSelector],
|
[stateSelector],
|
||||||
(state) => ({
|
(state) => ({
|
||||||
selectionCount: state.batch.selection.length,
|
selectionCount: state.batch.selection.length,
|
||||||
|
selection: state.batch.selection,
|
||||||
isSelected: state.batch.selection.includes(image_name),
|
isSelected: state.batch.selection.includes(image_name),
|
||||||
}),
|
}),
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
@ -30,43 +33,41 @@ type BatchImageProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BatchImage = (props: BatchImageProps) => {
|
const BatchImage = (props: BatchImageProps) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { imageName } = props;
|
||||||
const {
|
const {
|
||||||
currentData: imageDTO,
|
currentData: imageDTO,
|
||||||
isFetching,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
isSuccess,
|
isSuccess,
|
||||||
} = useGetImageDTOQuery(props.imageName);
|
} = useGetImageDTOQuery(imageName);
|
||||||
const dispatch = useAppDispatch();
|
const selector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||||
|
|
||||||
const selector = useMemo(
|
const { isSelected, selectionCount, selection } = useAppSelector(selector);
|
||||||
() => makeSelector(props.imageName),
|
|
||||||
[props.imageName]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isSelected, selectionCount } = useAppSelector(selector);
|
|
||||||
|
|
||||||
const handleClickRemove = useCallback(() => {
|
const handleClickRemove = useCallback(() => {
|
||||||
dispatch(imageRemovedFromBatch(props.imageName));
|
dispatch(imageRemovedFromBatch(imageName));
|
||||||
}, [dispatch, props.imageName]);
|
}, [dispatch, imageName]);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
(e: MouseEvent<HTMLDivElement>) => {
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
dispatch(batchImageRangeEndSelected(props.imageName));
|
dispatch(batchImageRangeEndSelected(imageName));
|
||||||
} else if (e.ctrlKey || e.metaKey) {
|
} else if (e.ctrlKey || e.metaKey) {
|
||||||
dispatch(batchImageSelectionToggled(props.imageName));
|
dispatch(batchImageSelectionToggled(imageName));
|
||||||
} else {
|
} else {
|
||||||
dispatch(batchImageSelected(props.imageName));
|
dispatch(batchImageSelected(imageName));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, props.imageName]
|
[dispatch, imageName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
|
const draggableData = useMemo<TypesafeDraggableData | undefined>(() => {
|
||||||
if (selectionCount > 1) {
|
if (selectionCount > 1) {
|
||||||
return {
|
return {
|
||||||
id: 'batch',
|
id: 'batch',
|
||||||
payloadType: 'BATCH_SELECTION',
|
payloadType: 'IMAGE_NAMES',
|
||||||
|
payload: { image_names: selection },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,38 +78,49 @@ const BatchImage = (props: BatchImageProps) => {
|
|||||||
payload: { imageDTO },
|
payload: { imageDTO },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [imageDTO, selectionCount]);
|
}, [imageDTO, selection, selectionCount]);
|
||||||
|
|
||||||
if (isError) {
|
if (isLoading) {
|
||||||
return <Icon as={FaExclamationCircle} />;
|
return <IAIFillSkeleton />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFetching) {
|
if (isError || !imageDTO) {
|
||||||
return (
|
return <IAIErrorLoadingImageFallback />;
|
||||||
<Skeleton>
|
|
||||||
<Box w="full" h="full" aspectRatio="1/1" />
|
|
||||||
</Skeleton>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ position: 'relative', aspectRatio: '1/1' }}>
|
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||||
<IAIDndImage
|
<ImageContextMenu imageDTO={imageDTO}>
|
||||||
imageDTO={imageDTO}
|
{(ref) => (
|
||||||
draggableData={draggableData}
|
<Box
|
||||||
isDropDisabled={true}
|
position="relative"
|
||||||
isUploadDisabled={true}
|
key={imageName}
|
||||||
imageSx={{
|
userSelect="none"
|
||||||
w: 'full',
|
ref={ref}
|
||||||
h: 'full',
|
sx={{
|
||||||
}}
|
display: 'flex',
|
||||||
onClick={handleClick}
|
justifyContent: 'center',
|
||||||
isSelected={isSelected}
|
alignItems: 'center',
|
||||||
onClickReset={handleClickRemove}
|
aspectRatio: '1/1',
|
||||||
resetTooltip="Remove from batch"
|
}}
|
||||||
withResetIcon
|
>
|
||||||
thumbnail
|
<IAIDndImage
|
||||||
/>
|
onClick={handleClick}
|
||||||
|
imageDTO={imageDTO}
|
||||||
|
draggableData={draggableData}
|
||||||
|
isSelected={isSelected}
|
||||||
|
minSize={0}
|
||||||
|
onClickReset={handleClickRemove}
|
||||||
|
isDropDisabled={true}
|
||||||
|
imageSx={{ w: 'full', h: 'full' }}
|
||||||
|
isUploadDisabled={true}
|
||||||
|
resetTooltip="Remove from batch"
|
||||||
|
withResetIcon
|
||||||
|
thumbnail
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</ImageContextMenu>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { AddToBatchDropData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import BatchImageGrid from './BatchImageGrid';
|
import BatchImageGrid from './BatchImageGrid';
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
|
||||||
import {
|
|
||||||
AddToBatchDropData,
|
|
||||||
isValidDrop,
|
|
||||||
useDroppable,
|
|
||||||
} from 'app/components/ImageDnd/typesafeDnd';
|
|
||||||
|
|
||||||
const droppableData: AddToBatchDropData = {
|
const droppableData: AddToBatchDropData = {
|
||||||
id: 'batch',
|
id: 'batch',
|
||||||
@ -13,17 +9,10 @@ const droppableData: AddToBatchDropData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BatchImageContainer = () => {
|
const BatchImageContainer = () => {
|
||||||
const { isOver, setNodeRef, active } = useDroppable({
|
|
||||||
id: 'batch-manager',
|
|
||||||
data: droppableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={setNodeRef} position="relative" w="full" h="full">
|
<Box position="relative" w="full" h="full">
|
||||||
<BatchImageGrid />
|
<BatchImageGrid />
|
||||||
{isValidDrop(droppableData, active) && (
|
<IAIDroppable data={droppableData} dropLabel="Add to Batch" />
|
||||||
<IAIDropOverlay isOver={isOver} label="Add to Batch" />
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PayloadAction, createAction, createSlice } from '@reduxjs/toolkit';
|
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||||
import { uniq } from 'lodash-es';
|
import { uniq } from 'lodash-es';
|
||||||
import { imageDeleted } from 'services/api/thunks/image';
|
import { imageDeleted } from 'services/api/thunks/image';
|
||||||
|
|
||||||
@ -26,10 +26,10 @@ const batch = createSlice({
|
|||||||
state.isEnabled = action.payload;
|
state.isEnabled = action.payload;
|
||||||
},
|
},
|
||||||
imageAddedToBatch: (state, action: PayloadAction<string>) => {
|
imageAddedToBatch: (state, action: PayloadAction<string>) => {
|
||||||
state.imageNames = uniq(state.imageNames.concat(action.payload));
|
state.imageNames.push(action.payload);
|
||||||
},
|
},
|
||||||
imagesAddedToBatch: (state, action: PayloadAction<string[]>) => {
|
imagesAddedToBatch: (state, action: PayloadAction<string[]>) => {
|
||||||
state.imageNames = uniq(state.imageNames.concat(action.payload));
|
state.imageNames = state.imageNames.concat(action.payload);
|
||||||
},
|
},
|
||||||
imageRemovedFromBatch: (state, action: PayloadAction<string>) => {
|
imageRemovedFromBatch: (state, action: PayloadAction<string>) => {
|
||||||
state.imageNames = state.imageNames.filter(
|
state.imageNames = state.imageNames.filter(
|
||||||
@ -50,10 +50,13 @@ const batch = createSlice({
|
|||||||
batchImageRangeEndSelected: (state, action: PayloadAction<string>) => {
|
batchImageRangeEndSelected: (state, action: PayloadAction<string>) => {
|
||||||
const rangeEndImageName = action.payload;
|
const rangeEndImageName = action.payload;
|
||||||
const lastSelectedImage = state.selection[state.selection.length - 1];
|
const lastSelectedImage = state.selection[state.selection.length - 1];
|
||||||
const lastClickedIndex = state.imageNames.findIndex(
|
|
||||||
|
const { imageNames } = state;
|
||||||
|
|
||||||
|
const lastClickedIndex = imageNames.findIndex(
|
||||||
(n) => n === lastSelectedImage
|
(n) => n === lastSelectedImage
|
||||||
);
|
);
|
||||||
const currentClickedIndex = state.imageNames.findIndex(
|
const currentClickedIndex = imageNames.findIndex(
|
||||||
(n) => n === rangeEndImageName
|
(n) => n === rangeEndImageName
|
||||||
);
|
);
|
||||||
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
||||||
@ -61,7 +64,8 @@ const batch = createSlice({
|
|||||||
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
||||||
const end = Math.max(lastClickedIndex, currentClickedIndex);
|
const end = Math.max(lastClickedIndex, currentClickedIndex);
|
||||||
|
|
||||||
const imagesToSelect = state.imageNames.slice(start, end + 1);
|
const imagesToSelect = imageNames.slice(start, end + 1);
|
||||||
|
|
||||||
state.selection = uniq(state.selection.concat(imagesToSelect));
|
state.selection = uniq(state.selection.concat(imagesToSelect));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -136,7 +140,3 @@ export const {
|
|||||||
} = batch.actions;
|
} = batch.actions;
|
||||||
|
|
||||||
export default batch.reducer;
|
export default batch.reducer;
|
||||||
|
|
||||||
export const selectionAddedToBatch = createAction(
|
|
||||||
'batch/selectionAddedToBatch'
|
|
||||||
);
|
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
|
|
||||||
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaImage } from 'react-icons/fa';
|
||||||
|
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import BatchImage from 'features/batch/components/BatchImage';
|
||||||
|
import { VirtuosoGrid } from 'react-virtuoso';
|
||||||
|
import ItemContainer from './ItemContainer';
|
||||||
|
import ListContainer from './ListContainer';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[stateSelector],
|
||||||
|
(state) => {
|
||||||
|
return {
|
||||||
|
imageNames: state.batch.imageNames,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
const BatchGrid = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||||
|
const [initialize, osInstance] = useOverlayScrollbars({
|
||||||
|
defer: true,
|
||||||
|
options: {
|
||||||
|
scrollbars: {
|
||||||
|
visibility: 'auto',
|
||||||
|
autoHide: 'leave',
|
||||||
|
autoHideDelay: 1300,
|
||||||
|
theme: 'os-theme-dark',
|
||||||
|
},
|
||||||
|
overflow: { x: 'hidden' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { imageNames } = useAppSelector(selector);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { current: root } = rootRef;
|
||||||
|
if (scroller && root) {
|
||||||
|
initialize({
|
||||||
|
target: root,
|
||||||
|
elements: {
|
||||||
|
viewport: scroller,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => osInstance()?.destroy();
|
||||||
|
}, [scroller, initialize, osInstance]);
|
||||||
|
|
||||||
|
if (imageNames.length) {
|
||||||
|
return (
|
||||||
|
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||||
|
<VirtuosoGrid
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
data={imageNames}
|
||||||
|
components={{
|
||||||
|
Item: ItemContainer,
|
||||||
|
List: ListContainer,
|
||||||
|
}}
|
||||||
|
scrollerRef={setScroller}
|
||||||
|
itemContent={(index, imageName) => (
|
||||||
|
<BatchImage key={imageName} imageName={imageName} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAINoContentFallback
|
||||||
|
label={t('gallery.noImagesInGallery')}
|
||||||
|
icon={FaImage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(BatchGrid);
|
@ -0,0 +1,102 @@
|
|||||||
|
import { Box, Spinner } from '@chakra-ui/react';
|
||||||
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
|
|
||||||
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaExclamation, FaImage } from 'react-icons/fa';
|
||||||
|
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import BatchImage from 'features/batch/components/BatchImage';
|
||||||
|
import { VirtuosoGrid } from 'react-virtuoso';
|
||||||
|
import { useGetAllBoardImagesForBoardQuery } from 'services/api/endpoints/boardImages';
|
||||||
|
import ItemContainer from './ItemContainer';
|
||||||
|
import ListContainer from './ListContainer';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[stateSelector],
|
||||||
|
(state) => {
|
||||||
|
return {
|
||||||
|
imageNames: state.batch.imageNames,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
type BoardGridProps = {
|
||||||
|
board_id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BoardGrid = (props: BoardGridProps) => {
|
||||||
|
const { board_id } = props;
|
||||||
|
const { data, isLoading, isError, isSuccess } =
|
||||||
|
useGetAllBoardImagesForBoardQuery({
|
||||||
|
board_id,
|
||||||
|
});
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const rootRef = useRef(null);
|
||||||
|
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||||
|
const [initialize, osInstance] = useOverlayScrollbars({
|
||||||
|
defer: true,
|
||||||
|
options: {
|
||||||
|
scrollbars: {
|
||||||
|
visibility: 'auto',
|
||||||
|
autoHide: 'leave',
|
||||||
|
autoHideDelay: 1300,
|
||||||
|
theme: 'os-theme-dark',
|
||||||
|
},
|
||||||
|
overflow: { x: 'hidden' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { current: root } = rootRef;
|
||||||
|
if (scroller && root) {
|
||||||
|
initialize({
|
||||||
|
target: root,
|
||||||
|
elements: {
|
||||||
|
viewport: scroller,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return () => osInstance()?.destroy();
|
||||||
|
}, [scroller, initialize, osInstance]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return <FaExclamation />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess && data.image_names) {
|
||||||
|
return (
|
||||||
|
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||||
|
<VirtuosoGrid
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
data={data.image_names}
|
||||||
|
components={{
|
||||||
|
Item: ItemContainer,
|
||||||
|
List: ListContainer,
|
||||||
|
}}
|
||||||
|
scrollerRef={setScroller}
|
||||||
|
itemContent={(index, imageName) => (
|
||||||
|
<BatchImage key={imageName} imageName={imageName} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAINoContentFallback
|
||||||
|
label={t('gallery.noImagesInGallery')}
|
||||||
|
icon={FaImage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(BoardGrid);
|
@ -1,23 +1,14 @@
|
|||||||
import { Flex, useColorMode } from '@chakra-ui/react';
|
import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import { FaImages } from 'react-icons/fa';
|
|
||||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { FaImages } from 'react-icons/fa';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import GenericBoard from './GenericBoard';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
|
||||||
import { mode } from 'theme/util/mode';
|
|
||||||
import {
|
|
||||||
MoveBoardDropData,
|
|
||||||
isValidDrop,
|
|
||||||
useDroppable,
|
|
||||||
} from 'app/components/ImageDnd/typesafeDnd';
|
|
||||||
|
|
||||||
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('all'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const droppableData: MoveBoardDropData = {
|
const droppableData: MoveBoardDropData = {
|
||||||
@ -26,67 +17,14 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
|||||||
context: { boardId: null },
|
context: { boardId: null },
|
||||||
};
|
};
|
||||||
|
|
||||||
const { isOver, setNodeRef, active } = useDroppable({
|
|
||||||
id: `board_droppable_all_images`,
|
|
||||||
data: droppableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<GenericBoard
|
||||||
sx={{
|
droppableData={droppableData}
|
||||||
flexDir: 'column',
|
onClick={handleAllImagesBoardClick}
|
||||||
justifyContent: 'space-between',
|
isSelected={isSelected}
|
||||||
alignItems: 'center',
|
icon={FaImages}
|
||||||
cursor: 'pointer',
|
label="All Images"
|
||||||
w: 'full',
|
/>
|
||||||
h: 'full',
|
|
||||||
borderRadius: 'base',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
ref={setNodeRef}
|
|
||||||
onClick={handleAllImagesBoardClick}
|
|
||||||
sx={{
|
|
||||||
position: 'relative',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 'base',
|
|
||||||
w: 'full',
|
|
||||||
aspectRatio: '1/1',
|
|
||||||
overflow: 'hidden',
|
|
||||||
shadow: isSelected ? 'selected.light' : undefined,
|
|
||||||
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IAINoContentFallback
|
|
||||||
boxSize={8}
|
|
||||||
icon={FaImages}
|
|
||||||
sx={{
|
|
||||||
border: '2px solid var(--invokeai-colors-base-200)',
|
|
||||||
_dark: { border: '2px solid var(--invokeai-colors-base-800)' },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<AnimatePresence>
|
|
||||||
{isValidDrop(droppableData, active) && (
|
|
||||||
<IAIDropOverlay isOver={isOver} />
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</Flex>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
color: isSelected
|
|
||||||
? mode('base.900', 'base.50')(colorMode)
|
|
||||||
: mode('base.700', 'base.200')(colorMode),
|
|
||||||
fontWeight: isSelected ? 600 : undefined,
|
|
||||||
fontSize: 'xs',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
All Images
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { AddToBatchDropData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { FaLayerGroup } from 'react-icons/fa';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import GenericBoard from './GenericBoard';
|
||||||
|
|
||||||
|
const selector = createSelector(stateSelector, (state) => {
|
||||||
|
return {
|
||||||
|
count: state.batch.imageNames.length,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const BatchBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { count } = useAppSelector(selector);
|
||||||
|
|
||||||
|
const handleBatchBoardClick = useCallback(() => {
|
||||||
|
dispatch(boardIdSelected('batch'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const droppableData: AddToBatchDropData = {
|
||||||
|
id: 'batch-board',
|
||||||
|
actionType: 'ADD_TO_BATCH',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GenericBoard
|
||||||
|
droppableData={droppableData}
|
||||||
|
onClick={handleBatchBoardClick}
|
||||||
|
isSelected={isSelected}
|
||||||
|
icon={FaLayerGroup}
|
||||||
|
label="Batch"
|
||||||
|
badgeCount={count}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BatchBoard;
|
@ -1,3 +1,4 @@
|
|||||||
|
import { CloseIcon } from '@chakra-ui/icons';
|
||||||
import {
|
import {
|
||||||
Collapse,
|
Collapse,
|
||||||
Flex,
|
Flex,
|
||||||
@ -9,17 +10,17 @@ import {
|
|||||||
InputRightElement,
|
InputRightElement,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { setBoardSearchText } from 'features/gallery/store/boardSlice';
|
import { setBoardSearchText } from 'features/gallery/store/boardSlice';
|
||||||
import { memo, useState } from 'react';
|
|
||||||
import HoverableBoard from './HoverableBoard';
|
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
import { memo, useState } from 'react';
|
||||||
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
import AddBoardButton from './AddBoardButton';
|
import AddBoardButton from './AddBoardButton';
|
||||||
import AllImagesBoard from './AllImagesBoard';
|
import AllImagesBoard from './AllImagesBoard';
|
||||||
import { CloseIcon } from '@chakra-ui/icons';
|
import BatchBoard from './BatchBoard';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import GalleryBoard from './GalleryBoard';
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
@ -115,14 +116,19 @@ const BoardsList = (props: Props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!searchMode && (
|
{!searchMode && (
|
||||||
<GridItem sx={{ p: 1.5 }}>
|
<>
|
||||||
<AllImagesBoard isSelected={!selectedBoardId} />
|
<GridItem sx={{ p: 1.5 }}>
|
||||||
</GridItem>
|
<AllImagesBoard isSelected={selectedBoardId === 'all'} />
|
||||||
|
</GridItem>
|
||||||
|
<GridItem sx={{ p: 1.5 }}>
|
||||||
|
<BatchBoard isSelected={selectedBoardId === 'batch'} />
|
||||||
|
</GridItem>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{filteredBoards &&
|
{filteredBoards &&
|
||||||
filteredBoards.map((board) => (
|
filteredBoards.map((board) => (
|
||||||
<GridItem key={board.board_id} sx={{ p: 1.5 }}>
|
<GridItem key={board.board_id} sx={{ p: 1.5 }}>
|
||||||
<HoverableBoard
|
<GalleryBoard
|
||||||
board={board}
|
board={board}
|
||||||
isSelected={selectedBoardId === board.board_id}
|
isSelected={selectedBoardId === board.board_id}
|
||||||
/>
|
/>
|
||||||
|
@ -12,35 +12,31 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { memo, useCallback, useContext } from 'react';
|
|
||||||
import { FaFolder, FaTrash } from 'react-icons/fa';
|
|
||||||
import { ContextMenu } from 'chakra-ui-contextmenu';
|
import { ContextMenu } from 'chakra-ui-contextmenu';
|
||||||
import { BoardDTO } from 'services/api/types';
|
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { memo, useCallback, useContext, useMemo } from 'react';
|
||||||
|
import { FaFolder, FaImages, FaTrash } from 'react-icons/fa';
|
||||||
import {
|
import {
|
||||||
useDeleteBoardMutation,
|
useDeleteBoardMutation,
|
||||||
useUpdateBoardMutation,
|
useUpdateBoardMutation,
|
||||||
} from 'services/api/endpoints/boards';
|
} from 'services/api/endpoints/boards';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
import { BoardDTO } from 'services/api/types';
|
||||||
|
|
||||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||||
import { AnimatePresence } from 'framer-motion';
|
import { MoveBoardDropData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
import { boardAddedToBatch } from 'app/store/middleware/listenerMiddleware/listeners/addBoardToBatch';
|
||||||
import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext';
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
import {
|
import { DeleteBoardImagesContext } from '../../../../app/contexts/DeleteBoardImagesContext';
|
||||||
MoveBoardDropData,
|
|
||||||
isValidDrop,
|
|
||||||
useDroppable,
|
|
||||||
} from 'app/components/ImageDnd/typesafeDnd';
|
|
||||||
|
|
||||||
interface HoverableBoardProps {
|
interface GalleryBoardProps {
|
||||||
board: BoardDTO;
|
board: BoardDTO;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
const GalleryBoard = memo(({ board, isSelected }: GalleryBoardProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { currentData: coverImage } = useGetImageDTOQuery(
|
const { currentData: coverImage } = useGetImageDTOQuery(
|
||||||
@ -71,21 +67,23 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
deleteBoard(board_id);
|
deleteBoard(board_id);
|
||||||
}, [board_id, deleteBoard]);
|
}, [board_id, deleteBoard]);
|
||||||
|
|
||||||
|
const handleAddBoardToBatch = useCallback(() => {
|
||||||
|
dispatch(boardAddedToBatch({ board_id }));
|
||||||
|
}, [board_id, dispatch]);
|
||||||
|
|
||||||
const handleDeleteBoardAndImages = useCallback(() => {
|
const handleDeleteBoardAndImages = useCallback(() => {
|
||||||
console.log({ board });
|
console.log({ board });
|
||||||
onClickDeleteBoardImages(board);
|
onClickDeleteBoardImages(board);
|
||||||
}, [board, onClickDeleteBoardImages]);
|
}, [board, onClickDeleteBoardImages]);
|
||||||
|
|
||||||
const droppableData: MoveBoardDropData = {
|
const droppableData: MoveBoardDropData = useMemo(
|
||||||
id: board_id,
|
() => ({
|
||||||
actionType: 'MOVE_BOARD',
|
id: board_id,
|
||||||
context: { boardId: board_id },
|
actionType: 'MOVE_BOARD',
|
||||||
};
|
context: { boardId: board_id },
|
||||||
|
}),
|
||||||
const { isOver, setNodeRef, active } = useDroppable({
|
[board_id]
|
||||||
id: `board_droppable_${board_id}`,
|
);
|
||||||
data: droppableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ touchAction: 'none', height: 'full' }}>
|
<Box sx={{ touchAction: 'none', height: 'full' }}>
|
||||||
@ -94,16 +92,25 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
renderMenu={() => (
|
renderMenu={() => (
|
||||||
<MenuList sx={{ visibility: 'visible !important' }}>
|
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||||
{board.image_count > 0 && (
|
{board.image_count > 0 && (
|
||||||
<MenuItem
|
<>
|
||||||
sx={{ color: 'error.300' }}
|
<MenuItem
|
||||||
icon={<FaTrash />}
|
isDisabled={!board.image_count}
|
||||||
onClickCapture={handleDeleteBoardAndImages}
|
icon={<FaImages />}
|
||||||
>
|
onClickCapture={handleAddBoardToBatch}
|
||||||
Delete Board and Images
|
>
|
||||||
</MenuItem>
|
Add Board to Batch
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||||
|
icon={<FaTrash />}
|
||||||
|
onClickCapture={handleDeleteBoardAndImages}
|
||||||
|
>
|
||||||
|
Delete Board and Images
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
sx={{ color: mode('error.700', 'error.300')(colorMode) }}
|
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
onClickCapture={handleDeleteBoard}
|
onClickCapture={handleDeleteBoard}
|
||||||
>
|
>
|
||||||
@ -127,7 +134,6 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
ref={setNodeRef}
|
|
||||||
onClick={handleSelectBoard}
|
onClick={handleSelectBoard}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@ -167,11 +173,7 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
>
|
>
|
||||||
<Badge variant="solid">{board.image_count}</Badge>
|
<Badge variant="solid">{board.image_count}</Badge>
|
||||||
</Flex>
|
</Flex>
|
||||||
<AnimatePresence>
|
<IAIDroppable data={droppableData} />
|
||||||
{isValidDrop(droppableData, active) && (
|
|
||||||
<IAIDropOverlay isOver={isOver} />
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
@ -219,6 +221,6 @@ const HoverableBoard = memo(({ board, isSelected }: HoverableBoardProps) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
HoverableBoard.displayName = 'HoverableBoard';
|
GalleryBoard.displayName = 'HoverableBoard';
|
||||||
|
|
||||||
export default HoverableBoard;
|
export default GalleryBoard;
|
@ -0,0 +1,83 @@
|
|||||||
|
import { As, Badge, Flex } from '@chakra-ui/react';
|
||||||
|
import { TypesafeDroppableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
|
import IAIDroppable from 'common/components/IAIDroppable';
|
||||||
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
|
||||||
|
type GenericBoardProps = {
|
||||||
|
droppableData: TypesafeDroppableData;
|
||||||
|
onClick: () => void;
|
||||||
|
isSelected: boolean;
|
||||||
|
icon: As;
|
||||||
|
label: string;
|
||||||
|
badgeCount?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenericBoard = (props: GenericBoardProps) => {
|
||||||
|
const { droppableData, onClick, isSelected, icon, label, badgeCount } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
flexDir: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
w: 'full',
|
||||||
|
h: 'full',
|
||||||
|
borderRadius: 'base',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
onClick={onClick}
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
borderRadius: 'base',
|
||||||
|
w: 'full',
|
||||||
|
aspectRatio: '1/1',
|
||||||
|
overflow: 'hidden',
|
||||||
|
shadow: isSelected ? 'selected.light' : undefined,
|
||||||
|
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IAINoContentFallback
|
||||||
|
boxSize={8}
|
||||||
|
icon={icon}
|
||||||
|
sx={{
|
||||||
|
border: '2px solid var(--invokeai-colors-base-200)',
|
||||||
|
_dark: { border: '2px solid var(--invokeai-colors-base-800)' },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
insetInlineEnd: 0,
|
||||||
|
top: 0,
|
||||||
|
p: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{badgeCount !== undefined && (
|
||||||
|
<Badge variant="solid">{badgeCount}</Badge>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
<IAIDroppable data={droppableData} />
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
h: 'full',
|
||||||
|
alignItems: 'center',
|
||||||
|
fontWeight: isSelected ? 600 : undefined,
|
||||||
|
fontSize: 'xs',
|
||||||
|
color: isSelected ? 'base.900' : 'base.700',
|
||||||
|
_dark: { color: isSelected ? 'base.50' : 'base.200' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GenericBoard;
|
@ -8,7 +8,7 @@ import {
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { selectLastSelectedImage } from 'features/gallery/store/gallerySlice';
|
import { selectLastSelectedImage } from 'features/gallery/store/gallerySelectors';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box, Spinner } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
import { TypesafeDraggableData } from 'app/components/ImageDnd/typesafeDnd';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
@ -7,9 +7,7 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
import IAIDndImage from 'common/components/IAIDndImage';
|
||||||
import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice';
|
import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice';
|
||||||
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
|
||||||
import { ImageDTO } from 'services/api/types';
|
|
||||||
import {
|
import {
|
||||||
imageRangeEndSelected,
|
imageRangeEndSelected,
|
||||||
imageSelected,
|
imageSelected,
|
||||||
@ -20,50 +18,38 @@ import ImageContextMenu from './ImageContextMenu';
|
|||||||
export const makeSelector = (image_name: string) =>
|
export const makeSelector = (image_name: string) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
({ gallery }) => {
|
({ gallery }) => ({
|
||||||
const isSelected = gallery.selection.includes(image_name);
|
isSelected: gallery.selection.includes(image_name),
|
||||||
const selectionCount = gallery.selection.length;
|
selectionCount: gallery.selection.length,
|
||||||
|
selection: gallery.selection,
|
||||||
return {
|
}),
|
||||||
isSelected,
|
|
||||||
selectionCount,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
interface HoverableImageProps {
|
interface HoverableImageProps {
|
||||||
imageDTO: ImageDTO;
|
imageName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gallery image component with delete/use all/use seed buttons on hover.
|
|
||||||
*/
|
|
||||||
const GalleryImage = (props: HoverableImageProps) => {
|
const GalleryImage = (props: HoverableImageProps) => {
|
||||||
const { imageDTO } = props;
|
|
||||||
const { image_url, thumbnail_url, image_name } = imageDTO;
|
|
||||||
|
|
||||||
const localSelector = useMemo(() => makeSelector(image_name), [image_name]);
|
|
||||||
|
|
||||||
const { isSelected, selectionCount } = useAppSelector(localSelector);
|
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const { imageName } = props;
|
||||||
|
const { currentData: imageDTO } = useGetImageDTOQuery(imageName);
|
||||||
|
const localSelector = useMemo(() => makeSelector(imageName), [imageName]);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { isSelected, selectionCount, selection } =
|
||||||
|
useAppSelector(localSelector);
|
||||||
|
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(e: MouseEvent<HTMLDivElement>) => {
|
(e: MouseEvent<HTMLDivElement>) => {
|
||||||
// multiselect disabled for now
|
if (e.shiftKey) {
|
||||||
// if (e.shiftKey) {
|
dispatch(imageRangeEndSelected(imageName));
|
||||||
// dispatch(imageRangeEndSelected(props.imageDTO.image_name));
|
} else if (e.ctrlKey || e.metaKey) {
|
||||||
// } else if (e.ctrlKey || e.metaKey) {
|
dispatch(imageSelectionToggled(imageName));
|
||||||
// dispatch(imageSelectionToggled(props.imageDTO.image_name));
|
} else {
|
||||||
// } else {
|
dispatch(imageSelected(imageName));
|
||||||
// dispatch(imageSelected(props.imageDTO.image_name));
|
}
|
||||||
// }
|
|
||||||
dispatch(imageSelected(props.imageDTO.image_name));
|
|
||||||
},
|
},
|
||||||
[dispatch, props.imageDTO.image_name]
|
[dispatch, imageName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
@ -81,7 +67,8 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
if (selectionCount > 1) {
|
if (selectionCount > 1) {
|
||||||
return {
|
return {
|
||||||
id: 'gallery-image',
|
id: 'gallery-image',
|
||||||
payloadType: 'GALLERY_SELECTION',
|
payloadType: 'IMAGE_NAMES',
|
||||||
|
payload: { image_names: selection },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,15 +79,19 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
payload: { imageDTO },
|
payload: { imageDTO },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [imageDTO, selectionCount]);
|
}, [imageDTO, selection, selectionCount]);
|
||||||
|
|
||||||
|
if (!imageDTO) {
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
<Box sx={{ w: 'full', h: 'full', touchAction: 'none' }}>
|
||||||
<ImageContextMenu image={imageDTO}>
|
<ImageContextMenu imageDTO={imageDTO}>
|
||||||
{(ref) => (
|
{(ref) => (
|
||||||
<Box
|
<Box
|
||||||
position="relative"
|
position="relative"
|
||||||
key={image_name}
|
key={imageName}
|
||||||
userSelect="none"
|
userSelect="none"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
sx={{
|
sx={{
|
||||||
@ -117,13 +108,13 @@ const GalleryImage = (props: HoverableImageProps) => {
|
|||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
minSize={0}
|
minSize={0}
|
||||||
onClickReset={handleDelete}
|
onClickReset={handleDelete}
|
||||||
resetIcon={<FaTrash />}
|
|
||||||
resetTooltip="Delete image"
|
|
||||||
imageSx={{ w: 'full', h: 'full' }}
|
imageSx={{ w: 'full', h: 'full' }}
|
||||||
// withResetIcon // removed bc it's too easy to accidentally delete images
|
|
||||||
isDropDisabled={true}
|
isDropDisabled={true}
|
||||||
isUploadDisabled={true}
|
isUploadDisabled={true}
|
||||||
thumbnail={true}
|
thumbnail={true}
|
||||||
|
// resetIcon={<FaTrash />}
|
||||||
|
// resetTooltip="Delete image"
|
||||||
|
// withResetIcon // removed bc it's too easy to accidentally delete images
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
@ -7,8 +7,9 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu';
|
||||||
import {
|
import {
|
||||||
|
imageAddedToBatch,
|
||||||
|
imageRemovedFromBatch,
|
||||||
imagesAddedToBatch,
|
imagesAddedToBatch,
|
||||||
selectionAddedToBatch,
|
|
||||||
} from 'features/batch/store/batchSlice';
|
} from 'features/batch/store/batchSlice';
|
||||||
import {
|
import {
|
||||||
resizeAndScaleCanvas,
|
resizeAndScaleCanvas,
|
||||||
@ -21,34 +22,46 @@ import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
|||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
import { memo, useCallback, useContext, useMemo } from 'react';
|
import { memo, useCallback, useContext, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaExpand, FaFolder, FaShare, FaTrash } from 'react-icons/fa';
|
import {
|
||||||
|
FaExpand,
|
||||||
|
FaFolder,
|
||||||
|
FaLayerGroup,
|
||||||
|
FaShare,
|
||||||
|
FaTrash,
|
||||||
|
} from 'react-icons/fa';
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages';
|
import {
|
||||||
|
useAddManyBoardImagesMutation,
|
||||||
|
useDeleteBoardImageMutation,
|
||||||
|
useDeleteManyBoardImagesMutation,
|
||||||
|
} from 'services/api/endpoints/boardImages';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import { ImageDTO } from 'services/api/types';
|
||||||
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
|
import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext';
|
||||||
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
image: ImageDTO;
|
imageDTO: ImageDTO;
|
||||||
children: ContextMenuProps<HTMLDivElement>['children'];
|
children: ContextMenuProps<HTMLDivElement>['children'];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImageContextMenu = ({ image, children }: Props) => {
|
const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
||||||
const selector = useMemo(
|
const selector = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(
|
createSelector(
|
||||||
[stateSelector],
|
[stateSelector],
|
||||||
({ gallery, batch }) => {
|
({ gallery, batch }) => {
|
||||||
const selectionCount = gallery.selection.length;
|
const isBatch = gallery.selectedBoardId === 'batch';
|
||||||
const isInBatch = batch.imageNames.includes(image.image_name);
|
|
||||||
|
|
||||||
return { selectionCount, isInBatch };
|
const selection = isBatch ? batch.selection : gallery.selection;
|
||||||
|
const isInBatch = batch.imageNames.includes(imageDTO.image_name);
|
||||||
|
|
||||||
|
return { selection, isInBatch };
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
),
|
),
|
||||||
[image.image_name]
|
[imageDTO.image_name]
|
||||||
);
|
);
|
||||||
const { selectionCount, isInBatch } = useAppSelector(selector);
|
const { selection, isInBatch } = useAppSelector(selector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -60,37 +73,39 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
const { onClickAddToBoard } = useContext(AddImageToBoardContext);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
if (!image) {
|
if (!imageDTO) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(imageToDeleteSelected(image));
|
dispatch(imageToDeleteSelected(imageDTO));
|
||||||
}, [dispatch, image]);
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||||
useRecallParameters();
|
useRecallParameters();
|
||||||
|
|
||||||
const [removeFromBoard] = useRemoveImageFromBoardMutation();
|
const [deleteBoardImage] = useDeleteBoardImageMutation();
|
||||||
|
const [deleteManyBoardImages] = useDeleteManyBoardImagesMutation();
|
||||||
|
const [addManyBoardImages] = useAddManyBoardImagesMutation();
|
||||||
|
|
||||||
// Recall parameters handlers
|
// Recall parameters handlers
|
||||||
const handleRecallPrompt = useCallback(() => {
|
const handleRecallPrompt = useCallback(() => {
|
||||||
recallBothPrompts(
|
recallBothPrompts(
|
||||||
image.metadata?.positive_conditioning,
|
imageDTO.metadata?.positive_conditioning,
|
||||||
image.metadata?.negative_conditioning
|
imageDTO.metadata?.negative_conditioning
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
image.metadata?.negative_conditioning,
|
imageDTO.metadata?.negative_conditioning,
|
||||||
image.metadata?.positive_conditioning,
|
imageDTO.metadata?.positive_conditioning,
|
||||||
recallBothPrompts,
|
recallBothPrompts,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleRecallSeed = useCallback(() => {
|
const handleRecallSeed = useCallback(() => {
|
||||||
recallSeed(image.metadata?.seed);
|
recallSeed(imageDTO.metadata?.seed);
|
||||||
}, [image, recallSeed]);
|
}, [imageDTO, recallSeed]);
|
||||||
|
|
||||||
const handleSendToImageToImage = useCallback(() => {
|
const handleSendToImageToImage = useCallback(() => {
|
||||||
dispatch(sentImageToImg2Img());
|
dispatch(sentImageToImg2Img());
|
||||||
dispatch(initialImageSelected(image));
|
dispatch(initialImageSelected(imageDTO));
|
||||||
}, [dispatch, image]);
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
// const handleRecallInitialImage = useCallback(() => {
|
// const handleRecallInitialImage = useCallback(() => {
|
||||||
// recallInitialImage(image.metadata.invokeai?.node?.image);
|
// recallInitialImage(image.metadata.invokeai?.node?.image);
|
||||||
@ -98,7 +113,7 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
|
|
||||||
const handleSendToCanvas = () => {
|
const handleSendToCanvas = () => {
|
||||||
dispatch(sentImageToCanvas());
|
dispatch(sentImageToCanvas());
|
||||||
dispatch(setInitialCanvasImage(image));
|
dispatch(setInitialCanvasImage(imageDTO));
|
||||||
dispatch(resizeAndScaleCanvas());
|
dispatch(resizeAndScaleCanvas());
|
||||||
dispatch(setActiveTab('unifiedCanvas'));
|
dispatch(setActiveTab('unifiedCanvas'));
|
||||||
|
|
||||||
@ -111,8 +126,8 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUseAllParameters = useCallback(() => {
|
const handleUseAllParameters = useCallback(() => {
|
||||||
recallAllParameters(image);
|
recallAllParameters(imageDTO);
|
||||||
}, [image, recallAllParameters]);
|
}, [imageDTO, recallAllParameters]);
|
||||||
|
|
||||||
const handleLightBox = () => {
|
const handleLightBox = () => {
|
||||||
// dispatch(setCurrentImage(image));
|
// dispatch(setCurrentImage(image));
|
||||||
@ -120,34 +135,50 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAddToBoard = useCallback(() => {
|
const handleAddToBoard = useCallback(() => {
|
||||||
onClickAddToBoard(image);
|
onClickAddToBoard(imageDTO);
|
||||||
}, [image, onClickAddToBoard]);
|
}, [imageDTO, onClickAddToBoard]);
|
||||||
|
|
||||||
const handleRemoveFromBoard = useCallback(() => {
|
const handleRemoveFromBoard = useCallback(() => {
|
||||||
if (!image.board_id) {
|
if (!imageDTO.board_id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
removeFromBoard({ board_id: image.board_id, image_name: image.image_name });
|
deleteBoardImage({ image_name: imageDTO.image_name });
|
||||||
}, [image.board_id, image.image_name, removeFromBoard]);
|
}, [deleteBoardImage, imageDTO.board_id, imageDTO.image_name]);
|
||||||
|
|
||||||
const handleOpenInNewTab = () => {
|
const handleAddSelectionToBoard = useCallback(() => {
|
||||||
window.open(image.image_url, '_blank');
|
// addManyBoardImages({ board_id, image_names: selection });
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
|
const handleRemoveSelectionFromBoard = useCallback(() => {
|
||||||
|
deleteManyBoardImages({ image_names: selection });
|
||||||
|
}, [deleteManyBoardImages, selection]);
|
||||||
|
|
||||||
|
const handleOpenInNewTab = useCallback(() => {
|
||||||
|
window.open(imageDTO.image_url, '_blank');
|
||||||
|
}, [imageDTO.image_url]);
|
||||||
|
|
||||||
const handleAddSelectionToBatch = useCallback(() => {
|
const handleAddSelectionToBatch = useCallback(() => {
|
||||||
dispatch(selectionAddedToBatch());
|
dispatch(imagesAddedToBatch(selection));
|
||||||
}, [dispatch]);
|
}, [dispatch, selection]);
|
||||||
|
|
||||||
const handleAddToBatch = useCallback(() => {
|
const handleAddToBatch = useCallback(() => {
|
||||||
dispatch(imagesAddedToBatch([image.image_name]));
|
dispatch(imageAddedToBatch(imageDTO.image_name));
|
||||||
}, [dispatch, image.image_name]);
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
|
const handleRemoveFromBatch = useCallback(() => {
|
||||||
|
dispatch(imageRemovedFromBatch(imageDTO.image_name));
|
||||||
|
}, [dispatch, imageDTO]);
|
||||||
|
|
||||||
|
if (!imageDTO) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu<HTMLDivElement>
|
<ContextMenu<HTMLDivElement>
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
renderMenu={() => (
|
renderMenu={() => (
|
||||||
<MenuList sx={{ visibility: 'visible !important' }}>
|
<MenuList sx={{ visibility: 'visible !important' }}>
|
||||||
{selectionCount === 1 ? (
|
{selection.length === 1 ? (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<ExternalLinkIcon />}
|
icon={<ExternalLinkIcon />}
|
||||||
@ -164,7 +195,7 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
icon={<IoArrowUndoCircleOutline />}
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
onClickCapture={handleRecallPrompt}
|
onClickCapture={handleRecallPrompt}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
image?.metadata?.positive_conditioning === undefined
|
imageDTO?.metadata?.positive_conditioning === undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t('parameters.usePrompt')}
|
{t('parameters.usePrompt')}
|
||||||
@ -173,24 +204,17 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
onClickCapture={handleRecallSeed}
|
onClickCapture={handleRecallSeed}
|
||||||
isDisabled={image?.metadata?.seed === undefined}
|
isDisabled={imageDTO?.metadata?.seed === undefined}
|
||||||
>
|
>
|
||||||
{t('parameters.useSeed')}
|
{t('parameters.useSeed')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/* <MenuItem
|
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
|
||||||
onClickCapture={handleRecallInitialImage}
|
|
||||||
isDisabled={image?.metadata?.type !== 'img2img'}
|
|
||||||
>
|
|
||||||
{t('parameters.useInitImg')}
|
|
||||||
</MenuItem> */}
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<IoArrowUndoCircleOutline />}
|
icon={<IoArrowUndoCircleOutline />}
|
||||||
onClickCapture={handleUseAllParameters}
|
onClickCapture={handleUseAllParameters}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
// what should these be
|
// what should these be
|
||||||
!['t2l', 'l2l', 'inpaint'].includes(
|
!['t2l', 'l2l', 'inpaint'].includes(
|
||||||
String(image?.metadata?.type)
|
String(imageDTO?.metadata?.type)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -212,17 +236,18 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
{t('parameters.sendToUnifiedCanvas')}
|
{t('parameters.sendToUnifiedCanvas')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{/* <MenuItem
|
<MenuItem
|
||||||
icon={<FaFolder />}
|
icon={<FaLayerGroup />}
|
||||||
isDisabled={isInBatch}
|
onClickCapture={
|
||||||
onClickCapture={handleAddToBatch}
|
isInBatch ? handleRemoveFromBatch : handleAddToBatch
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Add to Batch
|
{isInBatch ? 'Remove from Batch' : 'Add to Batch'}
|
||||||
</MenuItem> */}
|
|
||||||
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
|
||||||
{image.board_id ? 'Change Board' : 'Add to Board'}
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{image.board_id && (
|
<MenuItem icon={<FaFolder />} onClickCapture={handleAddToBoard}>
|
||||||
|
{imageDTO.board_id ? 'Change Board' : 'Add to Board'}
|
||||||
|
</MenuItem>
|
||||||
|
{imageDTO.board_id && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<FaFolder />}
|
icon={<FaFolder />}
|
||||||
onClickCapture={handleRemoveFromBoard}
|
onClickCapture={handleRemoveFromBoard}
|
||||||
@ -241,18 +266,23 @@ const ImageContextMenu = ({ image, children }: Props) => {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
isDisabled={true}
|
|
||||||
icon={<FaFolder />}
|
icon={<FaFolder />}
|
||||||
onClickCapture={handleAddToBoard}
|
onClickCapture={handleAddSelectionToBoard}
|
||||||
>
|
>
|
||||||
Move Selection to Board
|
Move Selection to Board
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{/* <MenuItem
|
<MenuItem
|
||||||
icon={<FaFolderPlus />}
|
icon={<FaFolder />}
|
||||||
|
onClickCapture={handleRemoveSelectionFromBoard}
|
||||||
|
>
|
||||||
|
Reset Board for Selection
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<FaLayerGroup />}
|
||||||
onClickCapture={handleAddSelectionToBatch}
|
onClickCapture={handleAddSelectionToBatch}
|
||||||
>
|
>
|
||||||
Add Selection to Batch
|
Add Selection to Batch
|
||||||
</MenuItem> */}
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
sx={{ color: 'error.600', _dark: { color: 'error.300' } }}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
import { togglePinGalleryPanel } from 'features/ui/store/uiSlice';
|
||||||
|
|
||||||
import { ChangeEvent, memo, useCallback, useRef } from 'react';
|
import { ChangeEvent, memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
import { BsPinAngle, BsPinAngleFill } from 'react-icons/bs';
|
||||||
import { FaImage, FaServer, FaWrench } from 'react-icons/fa';
|
import { FaImage, FaServer, FaWrench } from 'react-icons/fa';
|
||||||
@ -29,14 +29,10 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||||
import {
|
import { shouldAutoSwitchChanged } from 'features/gallery/store/gallerySlice';
|
||||||
ASSETS_CATEGORIES,
|
|
||||||
IMAGE_CATEGORIES,
|
|
||||||
imageCategoriesChanged,
|
|
||||||
shouldAutoSwitchChanged,
|
|
||||||
} from 'features/gallery/store/gallerySlice';
|
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
|
import BatchGrid from './BatchGrid';
|
||||||
import BoardsList from './Boards/BoardsList';
|
import BoardsList from './Boards/BoardsList';
|
||||||
import ImageGalleryGrid from './ImageGalleryGrid';
|
import ImageGalleryGrid from './ImageGalleryGrid';
|
||||||
|
|
||||||
@ -66,6 +62,7 @@ const ImageGalleryContent = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const resizeObserverRef = useRef<HTMLDivElement>(null);
|
const resizeObserverRef = useRef<HTMLDivElement>(null);
|
||||||
|
const galleryGridRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
@ -83,6 +80,16 @@ const ImageGalleryContent = () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const boardTitle = useMemo(() => {
|
||||||
|
if (selectedBoardId === 'batch') {
|
||||||
|
return 'Batch';
|
||||||
|
}
|
||||||
|
if (selectedBoard) {
|
||||||
|
return selectedBoard.board_name;
|
||||||
|
}
|
||||||
|
return 'All Images';
|
||||||
|
}, [selectedBoard, selectedBoardId]);
|
||||||
|
|
||||||
const { isOpen: isBoardListOpen, onToggle } = useDisclosure();
|
const { isOpen: isBoardListOpen, onToggle } = useDisclosure();
|
||||||
|
|
||||||
const handleChangeGalleryImageMinimumWidth = (v: number) => {
|
const handleChangeGalleryImageMinimumWidth = (v: number) => {
|
||||||
@ -95,12 +102,10 @@ const ImageGalleryContent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClickImagesCategory = useCallback(() => {
|
const handleClickImagesCategory = useCallback(() => {
|
||||||
dispatch(imageCategoriesChanged(IMAGE_CATEGORIES));
|
|
||||||
dispatch(setGalleryView('images'));
|
dispatch(setGalleryView('images'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleClickAssetsCategory = useCallback(() => {
|
const handleClickAssetsCategory = useCallback(() => {
|
||||||
dispatch(imageCategoriesChanged(ASSETS_CATEGORIES));
|
|
||||||
dispatch(setGalleryView('assets'));
|
dispatch(setGalleryView('assets'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
@ -163,7 +168,7 @@ const ImageGalleryContent = () => {
|
|||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{selectedBoard ? selectedBoard.board_name : 'All Images'}
|
{boardTitle}
|
||||||
</Text>
|
</Text>
|
||||||
<ChevronUpIcon
|
<ChevronUpIcon
|
||||||
sx={{
|
sx={{
|
||||||
@ -216,8 +221,8 @@ const ImageGalleryContent = () => {
|
|||||||
<BoardsList isOpen={isBoardListOpen} />
|
<BoardsList isOpen={isBoardListOpen} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex direction="column" gap={2} h="full" w="full">
|
<Flex ref={galleryGridRef} direction="column" gap={2} h="full" w="full">
|
||||||
<ImageGalleryGrid />
|
{selectedBoardId === 'batch' ? <BatchGrid /> : <ImageGalleryGrid />}
|
||||||
</Flex>
|
</Flex>
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
|
@ -1,74 +1,38 @@
|
|||||||
import {
|
import { Box } from '@chakra-ui/react';
|
||||||
Box,
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
Flex,
|
|
||||||
FlexProps,
|
|
||||||
Grid,
|
|
||||||
Skeleton,
|
|
||||||
Spinner,
|
|
||||||
forwardRef,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import { IMAGE_LIMIT } from 'features/gallery/store/gallerySlice';
|
|
||||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||||
|
|
||||||
import {
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
PropsWithChildren,
|
|
||||||
memo,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
import GalleryImage from './GalleryImage';
|
import GalleryImage from './GalleryImage';
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState, stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
import { selectFilteredImages } from 'features/gallery/store/gallerySlice';
|
|
||||||
import { VirtuosoGrid } from 'react-virtuoso';
|
import { VirtuosoGrid } from 'react-virtuoso';
|
||||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
import { useLoadMoreImages } from '../hooks/useLoadMoreImages';
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
import ItemContainer from './ItemContainer';
|
||||||
import { ImageDTO } from 'services/api/types';
|
import ListContainer from './ListContainer';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector, selectFilteredImages],
|
[stateSelector],
|
||||||
(state, filteredImages) => {
|
(state) => {
|
||||||
const {
|
const { galleryImageMinimumWidth } = state.gallery;
|
||||||
categories,
|
|
||||||
total: allImagesTotal,
|
|
||||||
isLoading,
|
|
||||||
isFetching,
|
|
||||||
selectedBoardId,
|
|
||||||
} = state.gallery;
|
|
||||||
|
|
||||||
let images = filteredImages as (ImageDTO | 'loading')[];
|
|
||||||
|
|
||||||
if (!isLoading && isFetching) {
|
|
||||||
// loading, not not the initial load
|
|
||||||
images = images.concat(Array(IMAGE_LIMIT).fill('loading'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
images,
|
galleryImageMinimumWidth,
|
||||||
allImagesTotal,
|
|
||||||
isLoading,
|
|
||||||
isFetching,
|
|
||||||
categories,
|
|
||||||
selectedBoardId,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
const ImageGalleryGrid = () => {
|
const ImageGalleryGrid = () => {
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const rootRef = useRef(null);
|
const rootRef = useRef<HTMLDivElement>(null);
|
||||||
|
const emptyGalleryRef = useRef<HTMLDivElement>(null);
|
||||||
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
const [scroller, setScroller] = useState<HTMLElement | null>(null);
|
||||||
const [initialize, osInstance] = useOverlayScrollbars({
|
const [initialize, osInstance] = useOverlayScrollbars({
|
||||||
defer: true,
|
defer: true,
|
||||||
@ -83,46 +47,27 @@ const ImageGalleryGrid = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { galleryImageMinimumWidth } = useAppSelector(selector);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
images,
|
imageNames,
|
||||||
isLoading,
|
galleryView,
|
||||||
isFetching,
|
loadMoreImages,
|
||||||
allImagesTotal,
|
|
||||||
categories,
|
|
||||||
selectedBoardId,
|
selectedBoardId,
|
||||||
} = useAppSelector(selector);
|
status,
|
||||||
|
areMoreAvailable,
|
||||||
const { selectedBoard } = useListAllBoardsQuery(undefined, {
|
} = useLoadMoreImages();
|
||||||
selectFromResult: ({ data }) => ({
|
|
||||||
selectedBoard: data?.find((b) => b.board_id === selectedBoardId),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredImagesTotal = useMemo(
|
|
||||||
() => selectedBoard?.image_count ?? allImagesTotal,
|
|
||||||
[allImagesTotal, selectedBoard?.image_count]
|
|
||||||
);
|
|
||||||
|
|
||||||
const areMoreAvailable = useMemo(() => {
|
|
||||||
return images.length < filteredImagesTotal;
|
|
||||||
}, [images.length, filteredImagesTotal]);
|
|
||||||
|
|
||||||
const handleLoadMoreImages = useCallback(() => {
|
const handleLoadMoreImages = useCallback(() => {
|
||||||
dispatch(
|
loadMoreImages({});
|
||||||
receivedPageOfImages({
|
}, [loadMoreImages]);
|
||||||
categories,
|
|
||||||
board_id: selectedBoardId,
|
|
||||||
is_intermediate: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [categories, dispatch, selectedBoardId]);
|
|
||||||
|
|
||||||
const handleEndReached = useMemo(() => {
|
const handleEndReached = useMemo(() => {
|
||||||
if (areMoreAvailable && !isLoading) {
|
if (areMoreAvailable && status !== 'pending') {
|
||||||
return handleLoadMoreImages;
|
return handleLoadMoreImages;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [areMoreAvailable, handleLoadMoreImages, isLoading]);
|
}, [areMoreAvailable, handleLoadMoreImages, status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { current: root } = rootRef;
|
const { current: root } = rootRef;
|
||||||
@ -137,53 +82,68 @@ const ImageGalleryGrid = () => {
|
|||||||
return () => osInstance()?.destroy();
|
return () => osInstance()?.destroy();
|
||||||
}, [scroller, initialize, osInstance]);
|
}, [scroller, initialize, osInstance]);
|
||||||
|
|
||||||
if (isLoading) {
|
useEffect(() => {
|
||||||
|
// TODO: this doesn't actually prevent 2 intial image loads...
|
||||||
|
if (status !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// rough, conservative calculation of how many images fit in the gallery
|
||||||
|
// TODO: this gets an incorrect value on first load...
|
||||||
|
const galleryHeight = rootRef.current?.clientHeight ?? 0;
|
||||||
|
const galleryWidth = rootRef.current?.clientHeight ?? 0;
|
||||||
|
|
||||||
|
const rows = galleryHeight / galleryImageMinimumWidth;
|
||||||
|
const columns = galleryWidth / galleryImageMinimumWidth;
|
||||||
|
|
||||||
|
const imagesToLoad = Math.ceil(rows * columns);
|
||||||
|
|
||||||
|
// load up that many images
|
||||||
|
loadMoreImages({
|
||||||
|
offset: 0,
|
||||||
|
limit: imagesToLoad,
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
galleryImageMinimumWidth,
|
||||||
|
galleryView,
|
||||||
|
loadMoreImages,
|
||||||
|
selectedBoardId,
|
||||||
|
status,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (status === 'fulfilled' && imageNames.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Box ref={emptyGalleryRef} sx={{ w: 'full', h: 'full' }}>
|
||||||
sx={{
|
<IAINoContentFallback
|
||||||
w: 'full',
|
label={t('gallery.noImagesInGallery')}
|
||||||
h: 'full',
|
icon={FaImage}
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Spinner
|
|
||||||
size="xl"
|
|
||||||
sx={{ color: 'base.300', _dark: { color: 'base.700' } }}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (images.length) {
|
if (status !== 'rejected') {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||||
<VirtuosoGrid
|
<VirtuosoGrid
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
data={images}
|
data={imageNames}
|
||||||
endReached={handleEndReached}
|
endReached={handleEndReached}
|
||||||
components={{
|
components={{
|
||||||
Item: ItemContainer,
|
Item: ItemContainer,
|
||||||
List: ListContainer,
|
List: ListContainer,
|
||||||
}}
|
}}
|
||||||
scrollerRef={setScroller}
|
scrollerRef={setScroller}
|
||||||
itemContent={(index, item) =>
|
itemContent={(index, imageName) => (
|
||||||
typeof item === 'string' ? (
|
<GalleryImage key={imageName} imageName={imageName} />
|
||||||
<Skeleton sx={{ w: 'full', h: 'full', aspectRatio: '1/1' }} />
|
)}
|
||||||
) : (
|
|
||||||
<GalleryImage
|
|
||||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
|
||||||
imageDTO={item}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={handleLoadMoreImages}
|
onClick={handleLoadMoreImages}
|
||||||
isDisabled={!areMoreAvailable}
|
isDisabled={!areMoreAvailable}
|
||||||
isLoading={isFetching}
|
isLoading={status === 'pending'}
|
||||||
loadingText="Loading"
|
loadingText="Loading"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
@ -194,40 +154,6 @@ const ImageGalleryGrid = () => {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<IAINoContentFallback
|
|
||||||
label={t('gallery.noImagesInGallery')}
|
|
||||||
icon={FaImage}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ItemContainerProps = PropsWithChildren & FlexProps;
|
|
||||||
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
|
|
||||||
<Box className="item-container" ref={ref} p={1.5}>
|
|
||||||
{props.children}
|
|
||||||
</Box>
|
|
||||||
));
|
|
||||||
|
|
||||||
type ListContainerProps = PropsWithChildren & FlexProps;
|
|
||||||
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
|
|
||||||
const galleryImageMinimumWidth = useAppSelector(
|
|
||||||
(state: RootState) => state.gallery.galleryImageMinimumWidth
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
{...props}
|
|
||||||
className="list-container"
|
|
||||||
ref={ref}
|
|
||||||
sx={{
|
|
||||||
gridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, 1fr));`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.children}
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default memo(ImageGalleryGrid);
|
export default memo(ImageGalleryGrid);
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Box, FlexProps, forwardRef } from '@chakra-ui/react';
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
type ItemContainerProps = PropsWithChildren & FlexProps;
|
||||||
|
const ItemContainer = forwardRef((props: ItemContainerProps, ref) => (
|
||||||
|
<Box className="item-container" ref={ref} p={1.5}>
|
||||||
|
{props.children}
|
||||||
|
</Box>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default ItemContainer;
|
@ -0,0 +1,26 @@
|
|||||||
|
import { FlexProps, Grid, forwardRef } from '@chakra-ui/react';
|
||||||
|
import { RootState } from 'app/store/store';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
type ListContainerProps = PropsWithChildren & FlexProps;
|
||||||
|
const ListContainer = forwardRef((props: ListContainerProps, ref) => {
|
||||||
|
const galleryImageMinimumWidth = useAppSelector(
|
||||||
|
(state: RootState) => state.gallery.galleryImageMinimumWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
{...props}
|
||||||
|
className="list-container"
|
||||||
|
ref={ref}
|
||||||
|
sx={{
|
||||||
|
gridTemplateColumns: `repeat(auto-fill, minmax(${galleryImageMinimumWidth}px, 1fr));`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ListContainer;
|
@ -4,7 +4,6 @@ import { stateSelector } from 'app/store/store';
|
|||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import {
|
import {
|
||||||
imageSelected,
|
imageSelected,
|
||||||
selectFilteredImages,
|
|
||||||
selectImagesById,
|
selectImagesById,
|
||||||
} from 'features/gallery/store/gallerySlice';
|
} from 'features/gallery/store/gallerySlice';
|
||||||
import { clamp, isEqual } from 'lodash-es';
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
@ -13,6 +12,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa';
|
||||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||||
|
import { selectFilteredImages } from '../store/gallerySelectors';
|
||||||
|
|
||||||
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
const nextPrevButtonTriggerAreaStyles: ChakraProps['sx'] = {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { ImagesLoadedArg, imagesLoaded } from 'services/api/thunks/image';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
[stateSelector],
|
||||||
|
(state) => {
|
||||||
|
const { selectedBoardId, galleryView } = state.gallery;
|
||||||
|
|
||||||
|
const imageNames =
|
||||||
|
state.gallery.imageNamesByIdAndView[selectedBoardId]?.[galleryView]
|
||||||
|
.imageNames ?? [];
|
||||||
|
|
||||||
|
const total =
|
||||||
|
state.gallery.imageNamesByIdAndView[selectedBoardId]?.[galleryView]
|
||||||
|
.total ?? 0;
|
||||||
|
|
||||||
|
const status =
|
||||||
|
state.gallery.statusByIdAndView[selectedBoardId]?.[galleryView] ??
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
imageNames,
|
||||||
|
status,
|
||||||
|
total,
|
||||||
|
selectedBoardId,
|
||||||
|
galleryView,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useLoadMoreImages = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { selectedBoardId, imageNames, galleryView, total, status } =
|
||||||
|
useAppSelector(selector);
|
||||||
|
|
||||||
|
const loadMoreImages = useCallback(
|
||||||
|
(arg: Partial<ImagesLoadedArg>) => {
|
||||||
|
dispatch(
|
||||||
|
imagesLoaded({
|
||||||
|
board_id: selectedBoardId,
|
||||||
|
offset: imageNames.length,
|
||||||
|
view: galleryView,
|
||||||
|
...arg,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[dispatch, galleryView, imageNames.length, selectedBoardId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
loadMoreImages,
|
||||||
|
selectedBoardId,
|
||||||
|
imageNames,
|
||||||
|
galleryView,
|
||||||
|
areMoreAvailable: imageNames.length < total,
|
||||||
|
total,
|
||||||
|
status,
|
||||||
|
};
|
||||||
|
};
|
@ -15,4 +15,6 @@ export const galleryPersistDenylist: (keyof typeof initialGalleryState)[] = [
|
|||||||
'galleryView',
|
'galleryView',
|
||||||
'total',
|
'total',
|
||||||
'isInitialized',
|
'isInitialized',
|
||||||
|
'imageNamesByIdAndView',
|
||||||
|
'statusByIdAndView',
|
||||||
];
|
];
|
||||||
|
@ -1,3 +1,61 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import { keyBy } from 'lodash-es';
|
||||||
|
import { galleryImagesAdapter, initialGalleryState } from './gallerySlice';
|
||||||
|
|
||||||
export const gallerySelector = (state: RootState) => state.gallery;
|
export const gallerySelector = (state: RootState) => state.gallery;
|
||||||
|
|
||||||
|
export const selectFilteredImagesLocal = createSelector(
|
||||||
|
(state: typeof initialGalleryState) => state,
|
||||||
|
(galleryState) => {
|
||||||
|
const allImages = galleryImagesAdapter
|
||||||
|
.getSelectors()
|
||||||
|
.selectAll(galleryState);
|
||||||
|
const { categories, selectedBoardId } = galleryState;
|
||||||
|
|
||||||
|
const filteredImages = allImages.filter((i) => {
|
||||||
|
const isInCategory = categories.includes(i.image_category);
|
||||||
|
const isInSelectedBoard = selectedBoardId
|
||||||
|
? i.board_id === selectedBoardId
|
||||||
|
: true;
|
||||||
|
return isInCategory && isInSelectedBoard;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filteredImages;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectFilteredImages = createSelector(
|
||||||
|
(state: RootState) => state,
|
||||||
|
(state) => {
|
||||||
|
return selectFilteredImagesLocal(state.gallery);
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectFilteredImagesAsObject = createSelector(
|
||||||
|
selectFilteredImages,
|
||||||
|
(filteredImages) => keyBy(filteredImages, 'image_name')
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectFilteredImagesIds = createSelector(
|
||||||
|
selectFilteredImages,
|
||||||
|
(filteredImages) => filteredImages.map((i) => i.image_name)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectLastSelectedImage = createSelector(
|
||||||
|
(state: RootState) => state,
|
||||||
|
(state) => state.gallery.selection[state.gallery.selection.length - 1],
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectSelectedImages = createSelector(
|
||||||
|
(state: RootState) => state,
|
||||||
|
(state) =>
|
||||||
|
galleryImagesAdapter
|
||||||
|
.getSelectors()
|
||||||
|
.selectAll(state.gallery)
|
||||||
|
.filter((i) => state.gallery.selection.includes(i.image_name)),
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
@ -1,21 +1,14 @@
|
|||||||
import type { PayloadAction, Update } from '@reduxjs/toolkit';
|
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||||
import {
|
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
|
||||||
createEntityAdapter,
|
|
||||||
createSelector,
|
|
||||||
createSlice,
|
|
||||||
} from '@reduxjs/toolkit';
|
|
||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import { dateComparator } from 'common/util/dateComparator';
|
import { dateComparator } from 'common/util/dateComparator';
|
||||||
import { keyBy, uniq } from 'lodash-es';
|
import { filter, forEach, uniq } from 'lodash-es';
|
||||||
|
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||||
import { boardsApi } from 'services/api/endpoints/boards';
|
import { boardsApi } from 'services/api/endpoints/boards';
|
||||||
import {
|
import { imageDeleted, imagesLoaded } from 'services/api/thunks/image';
|
||||||
imageUrlsReceived,
|
|
||||||
receivedPageOfImages,
|
|
||||||
} from 'services/api/thunks/image';
|
|
||||||
import { ImageCategory, ImageDTO } from 'services/api/types';
|
import { ImageCategory, ImageDTO } from 'services/api/types';
|
||||||
|
|
||||||
export const imagesAdapter = createEntityAdapter<ImageDTO>({
|
export const galleryImagesAdapter = createEntityAdapter<ImageDTO>({
|
||||||
selectId: (image) => image.image_name,
|
selectId: (image) => image.image_name,
|
||||||
sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at),
|
sortComparer: (a, b) => dateComparator(b.updated_at, a.updated_at),
|
||||||
});
|
});
|
||||||
@ -31,23 +24,99 @@ export const ASSETS_CATEGORIES: ImageCategory[] = [
|
|||||||
export const INITIAL_IMAGE_LIMIT = 100;
|
export const INITIAL_IMAGE_LIMIT = 100;
|
||||||
export const IMAGE_LIMIT = 20;
|
export const IMAGE_LIMIT = 20;
|
||||||
|
|
||||||
type AdditionaGalleryState = {
|
type RequestState = 'pending' | 'fulfilled' | 'rejected';
|
||||||
|
type GalleryView = 'images' | 'assets';
|
||||||
|
|
||||||
|
// dirty hack to get autocompletion while still accepting any string
|
||||||
|
type BoardPath =
|
||||||
|
| 'all.images'
|
||||||
|
| 'all.assets'
|
||||||
|
| 'none.images'
|
||||||
|
| 'none.assets'
|
||||||
|
| 'batch.images'
|
||||||
|
| 'batch.assets'
|
||||||
|
| `${string}.${GalleryView}`;
|
||||||
|
|
||||||
|
const systemBoards = [
|
||||||
|
'all.images',
|
||||||
|
'all.assets',
|
||||||
|
'none.images',
|
||||||
|
'none.assets',
|
||||||
|
'batch.images',
|
||||||
|
'batch.assets',
|
||||||
|
];
|
||||||
|
|
||||||
|
type Boards = Record<
|
||||||
|
BoardPath,
|
||||||
|
{
|
||||||
|
path: BoardPath;
|
||||||
|
id: 'all' | 'none' | 'batch' | (string & Record<never, never>);
|
||||||
|
view: GalleryView;
|
||||||
|
imageNames: string[];
|
||||||
|
total: number;
|
||||||
|
status: RequestState | undefined;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
type AdditionalGalleryState = {
|
||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
total: number;
|
total: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
categories: ImageCategory[];
|
categories: ImageCategory[];
|
||||||
selectedBoardId?: string;
|
|
||||||
selection: string[];
|
selection: string[];
|
||||||
shouldAutoSwitch: boolean;
|
shouldAutoSwitch: boolean;
|
||||||
galleryImageMinimumWidth: number;
|
galleryImageMinimumWidth: number;
|
||||||
galleryView: 'images' | 'assets';
|
|
||||||
isInitialized: boolean;
|
isInitialized: boolean;
|
||||||
|
galleryView: GalleryView;
|
||||||
|
selectedBoardId: 'all' | 'none' | 'batch' | (string & Record<never, never>);
|
||||||
|
boards: Boards;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialBoardState = { imageNames: [], total: 0, status: undefined };
|
||||||
|
|
||||||
|
const initialBoards: Boards = {
|
||||||
|
'all.images': {
|
||||||
|
path: 'all.images',
|
||||||
|
id: 'all',
|
||||||
|
view: 'images',
|
||||||
|
...initialBoardState,
|
||||||
|
},
|
||||||
|
'all.assets': {
|
||||||
|
path: 'all.assets',
|
||||||
|
id: 'all',
|
||||||
|
view: 'assets',
|
||||||
|
...initialBoardState,
|
||||||
|
},
|
||||||
|
'none.images': {
|
||||||
|
path: 'none.images',
|
||||||
|
id: 'none',
|
||||||
|
view: 'images',
|
||||||
|
...initialBoardState,
|
||||||
|
},
|
||||||
|
'none.assets': {
|
||||||
|
path: 'none.assets',
|
||||||
|
id: 'none',
|
||||||
|
view: 'assets',
|
||||||
|
...initialBoardState,
|
||||||
|
},
|
||||||
|
'batch.images': {
|
||||||
|
path: 'batch.images',
|
||||||
|
id: 'batch',
|
||||||
|
view: 'images',
|
||||||
|
...initialBoardState,
|
||||||
|
},
|
||||||
|
'batch.assets': {
|
||||||
|
path: 'batch.assets',
|
||||||
|
id: 'batch',
|
||||||
|
view: 'assets',
|
||||||
|
...initialBoardState,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialGalleryState =
|
export const initialGalleryState =
|
||||||
imagesAdapter.getInitialState<AdditionaGalleryState>({
|
galleryImagesAdapter.getInitialState<AdditionalGalleryState>({
|
||||||
offset: 0,
|
offset: 0,
|
||||||
limit: 0,
|
limit: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
@ -59,57 +128,45 @@ export const initialGalleryState =
|
|||||||
galleryImageMinimumWidth: 96,
|
galleryImageMinimumWidth: 96,
|
||||||
galleryView: 'images',
|
galleryView: 'images',
|
||||||
isInitialized: false,
|
isInitialized: false,
|
||||||
|
selectedBoardId: 'all',
|
||||||
|
boards: initialBoards,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const gallerySlice = createSlice({
|
export const gallerySlice = createSlice({
|
||||||
name: 'gallery',
|
name: 'gallery',
|
||||||
initialState: initialGalleryState,
|
initialState: initialGalleryState,
|
||||||
reducers: {
|
reducers: {
|
||||||
imageUpserted: (state, action: PayloadAction<ImageDTO>) => {
|
|
||||||
imagesAdapter.upsertOne(state, action.payload);
|
|
||||||
if (
|
|
||||||
state.shouldAutoSwitch &&
|
|
||||||
action.payload.image_category === 'general'
|
|
||||||
) {
|
|
||||||
state.selection = [action.payload.image_name];
|
|
||||||
state.galleryView = 'images';
|
|
||||||
state.categories = IMAGE_CATEGORIES;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
imageUpdatedOne: (state, action: PayloadAction<Update<ImageDTO>>) => {
|
|
||||||
imagesAdapter.updateOne(state, action.payload);
|
|
||||||
},
|
|
||||||
imageRemoved: (state, action: PayloadAction<string>) => {
|
imageRemoved: (state, action: PayloadAction<string>) => {
|
||||||
imagesAdapter.removeOne(state, action.payload);
|
galleryImagesAdapter.removeOne(state, action.payload);
|
||||||
},
|
},
|
||||||
imagesRemoved: (state, action: PayloadAction<string[]>) => {
|
imagesRemoved: (state, action: PayloadAction<string[]>) => {
|
||||||
imagesAdapter.removeMany(state, action.payload);
|
galleryImagesAdapter.removeMany(state, action.payload);
|
||||||
},
|
|
||||||
imageCategoriesChanged: (state, action: PayloadAction<ImageCategory[]>) => {
|
|
||||||
state.categories = action.payload;
|
|
||||||
},
|
},
|
||||||
imageRangeEndSelected: (state, action: PayloadAction<string>) => {
|
imageRangeEndSelected: (state, action: PayloadAction<string>) => {
|
||||||
const rangeEndImageName = action.payload;
|
const rangeEndImageName = action.payload;
|
||||||
const lastSelectedImage = state.selection[state.selection.length - 1];
|
const lastSelectedImage = state.selection[state.selection.length - 1];
|
||||||
|
|
||||||
const filteredImages = selectFilteredImagesLocal(state);
|
// get image names for the current board and view
|
||||||
|
const imageNames =
|
||||||
|
state.boards[`${state.selectedBoardId}.${state.galleryView}`]
|
||||||
|
.imageNames;
|
||||||
|
|
||||||
const lastClickedIndex = filteredImages.findIndex(
|
// get the index of the last selected image
|
||||||
(n) => n.image_name === lastSelectedImage
|
const lastClickedIndex = imageNames.findIndex(
|
||||||
|
(n) => n === lastSelectedImage
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentClickedIndex = filteredImages.findIndex(
|
// get the index of the just-clicked image
|
||||||
(n) => n.image_name === rangeEndImageName
|
const currentClickedIndex = imageNames.findIndex(
|
||||||
|
(n) => n === rangeEndImageName
|
||||||
);
|
);
|
||||||
|
|
||||||
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
||||||
// We have a valid range!
|
// We have a valid range, selected it!
|
||||||
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
||||||
const end = Math.max(lastClickedIndex, currentClickedIndex);
|
const end = Math.max(lastClickedIndex, currentClickedIndex);
|
||||||
|
|
||||||
const imagesToSelect = filteredImages
|
const imagesToSelect = imageNames.slice(start, end + 1);
|
||||||
.slice(start, end + 1)
|
|
||||||
.map((i) => i.image_name);
|
|
||||||
|
|
||||||
state.selection = uniq(state.selection.concat(imagesToSelect));
|
state.selection = uniq(state.selection.concat(imagesToSelect));
|
||||||
}
|
}
|
||||||
@ -122,9 +179,10 @@ export const gallerySlice = createSlice({
|
|||||||
state.selection = state.selection.filter(
|
state.selection = state.selection.filter(
|
||||||
(imageName) => imageName !== action.payload
|
(imageName) => imageName !== action.payload
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
state.selection = uniq(state.selection.concat(action.payload));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.selection = uniq(state.selection.concat(action.payload));
|
||||||
},
|
},
|
||||||
imageSelected: (state, action: PayloadAction<string | null>) => {
|
imageSelected: (state, action: PayloadAction<string | null>) => {
|
||||||
state.selection = action.payload
|
state.selection = action.payload
|
||||||
@ -137,59 +195,210 @@ export const gallerySlice = createSlice({
|
|||||||
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
|
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
|
||||||
state.galleryImageMinimumWidth = action.payload;
|
state.galleryImageMinimumWidth = action.payload;
|
||||||
},
|
},
|
||||||
setGalleryView: (state, action: PayloadAction<'images' | 'assets'>) => {
|
setGalleryView: (state, action: PayloadAction<GalleryView>) => {
|
||||||
state.galleryView = action.payload;
|
state.galleryView = action.payload;
|
||||||
},
|
},
|
||||||
boardIdSelected: (state, action: PayloadAction<string | undefined>) => {
|
boardIdSelected: (state, action: PayloadAction<BoardPath>) => {
|
||||||
state.selectedBoardId = action.payload;
|
const boardId = action.payload;
|
||||||
|
|
||||||
|
if (state.selectedBoardId === boardId) {
|
||||||
|
// selected same board, no-op
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.selectedBoardId = boardId;
|
||||||
|
|
||||||
|
// handle selecting an unitialized board
|
||||||
|
const boardImagesId: BoardPath = `${boardId}.images`;
|
||||||
|
const boardAssetsId: BoardPath = `${boardId}.assets`;
|
||||||
|
|
||||||
|
if (!state.boards[boardImagesId]) {
|
||||||
|
state.boards[boardImagesId] = {
|
||||||
|
path: boardImagesId,
|
||||||
|
id: boardId,
|
||||||
|
view: 'images',
|
||||||
|
...initialBoardState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.boards[boardAssetsId]) {
|
||||||
|
state.boards[boardAssetsId] = {
|
||||||
|
path: boardAssetsId,
|
||||||
|
id: boardId,
|
||||||
|
view: 'assets',
|
||||||
|
...initialBoardState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the first image as selected
|
||||||
|
const firstImageName =
|
||||||
|
state.boards[`${boardId}.${state.galleryView}`].imageNames[0];
|
||||||
|
|
||||||
|
state.selection = firstImageName ? [firstImageName] : [];
|
||||||
},
|
},
|
||||||
isLoadingChanged: (state, action: PayloadAction<boolean>) => {
|
isLoadingChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
state.isLoading = action.payload;
|
state.isLoading = action.payload;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(receivedPageOfImages.pending, (state) => {
|
/**
|
||||||
state.isFetching = true;
|
* Image deleted
|
||||||
|
*/
|
||||||
|
builder.addCase(imageDeleted.pending, (state, action) => {
|
||||||
|
// optimistic update, but no undo :/
|
||||||
|
const { image_name } = action.meta.arg;
|
||||||
|
// remove image from all boards
|
||||||
|
forEach(state.boards, (board) => {
|
||||||
|
board.imageNames = board.imageNames.filter((n) => n !== image_name);
|
||||||
|
});
|
||||||
|
// and selection
|
||||||
|
state.selection = state.selection.filter((n) => n !== image_name);
|
||||||
});
|
});
|
||||||
builder.addCase(receivedPageOfImages.rejected, (state) => {
|
/**
|
||||||
state.isFetching = false;
|
* Images loaded into gallery - PENDING
|
||||||
|
*/
|
||||||
|
builder.addCase(imagesLoaded.pending, (state, action) => {
|
||||||
|
const { board_id, view } = action.meta.arg;
|
||||||
|
state.boards[`${board_id}.${view}`].status = 'pending';
|
||||||
});
|
});
|
||||||
builder.addCase(receivedPageOfImages.fulfilled, (state, action) => {
|
/**
|
||||||
state.isFetching = false;
|
* Images loaded into gallery - FULFILLED
|
||||||
const { board_id, categories, image_origin, is_intermediate } =
|
*/
|
||||||
action.meta.arg;
|
builder.addCase(imagesLoaded.fulfilled, (state, action) => {
|
||||||
|
const { items, total } = action.payload;
|
||||||
|
const { board_id, view } = action.meta.arg;
|
||||||
|
const board = state.boards[`${board_id}.${view}`];
|
||||||
|
|
||||||
const { items, offset, limit, total } = action.payload;
|
board.status = 'fulfilled';
|
||||||
|
|
||||||
imagesAdapter.upsertMany(state, items);
|
board.imageNames = uniq(
|
||||||
|
board.imageNames.concat(items.map((i) => i.image_name))
|
||||||
|
);
|
||||||
|
|
||||||
|
board.total = total;
|
||||||
|
|
||||||
if (state.selection.length === 0 && items.length) {
|
if (state.selection.length === 0 && items.length) {
|
||||||
state.selection = [items[0].image_name];
|
state.selection = [items[0].image_name];
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Images loaded into gallery - REJECTED
|
||||||
|
*/
|
||||||
|
builder.addCase(imagesLoaded.rejected, (state, action) => {
|
||||||
|
const { board_id, view } = action.meta.arg;
|
||||||
|
state.boards[`${board_id}.${view}`].status = 'rejected';
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* Image added to board
|
||||||
|
*/
|
||||||
|
builder.addMatcher(
|
||||||
|
boardImagesApi.endpoints.addBoardImage.matchFulfilled,
|
||||||
|
(state, action) => {
|
||||||
|
const { board_id, image_name } = action.meta.arg.originalArgs;
|
||||||
|
// update user board stores
|
||||||
|
const userBoards = selectUserBoards(state);
|
||||||
|
userBoards.forEach((board) => {
|
||||||
|
// only update the current view
|
||||||
|
if (board.view !== state.galleryView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!categories?.includes('general') || board_id) {
|
if (board_id === board.id) {
|
||||||
// need to skip updating the total images count if the images recieved were for a specific board
|
// add image to the board
|
||||||
// TODO: this doesn't work when on the Asset tab/category...
|
board.imageNames = uniq(board.imageNames.concat(image_name));
|
||||||
return;
|
} else {
|
||||||
|
// remove image from other boards
|
||||||
|
board.imageNames = board.imageNames.filter((n) => n !== image_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
);
|
||||||
state.offset = offset;
|
/**
|
||||||
state.total = total;
|
* Many images added to board
|
||||||
});
|
*/
|
||||||
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
|
builder.addMatcher(
|
||||||
const { image_name, image_url, thumbnail_url } = action.payload;
|
boardImagesApi.endpoints.addManyBoardImages.matchFulfilled,
|
||||||
|
(state, action) => {
|
||||||
imagesAdapter.updateOne(state, {
|
const { board_id, image_names } = action.meta.arg.originalArgs;
|
||||||
id: image_name,
|
// update local board stores
|
||||||
changes: { image_url, thumbnail_url },
|
forEach(state.boards, (board, board_id) => {
|
||||||
});
|
// only update the current view
|
||||||
});
|
if (board_id === board.id) {
|
||||||
|
// add images to the board
|
||||||
|
board.imageNames = uniq(board.imageNames.concat(image_names));
|
||||||
|
} else {
|
||||||
|
// remove images from other boards
|
||||||
|
board.imageNames = board.imageNames.filter((n) =>
|
||||||
|
image_names.includes(n)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Board deleted (not images)
|
||||||
|
*/
|
||||||
builder.addMatcher(
|
builder.addMatcher(
|
||||||
boardsApi.endpoints.deleteBoard.matchFulfilled,
|
boardsApi.endpoints.deleteBoard.matchFulfilled,
|
||||||
(state, action) => {
|
(state, action) => {
|
||||||
if (action.meta.arg.originalArgs === state.selectedBoardId) {
|
const deletedBoardId = action.meta.arg.originalArgs;
|
||||||
state.selectedBoardId = undefined;
|
if (deletedBoardId === state.selectedBoardId) {
|
||||||
|
state.selectedBoardId = 'all';
|
||||||
}
|
}
|
||||||
|
// remove board from local store
|
||||||
|
delete state.boards[`${deletedBoardId}.images`];
|
||||||
|
delete state.boards[`${deletedBoardId}.assets`];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Board deleted (with images)
|
||||||
|
*/
|
||||||
|
builder.addMatcher(
|
||||||
|
boardsApi.endpoints.deleteBoardAndImages.matchFulfilled,
|
||||||
|
(state, action) => {
|
||||||
|
const { deleted_images } = action.payload;
|
||||||
|
const deletedBoardId = action.meta.arg.originalArgs;
|
||||||
|
// remove images from all boards
|
||||||
|
forEach(state.boards, (board) => {
|
||||||
|
// remove images from all boards
|
||||||
|
board.imageNames = board.imageNames.filter((n) =>
|
||||||
|
deleted_images.includes(n)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
delete state.boards[`${deletedBoardId}.images`];
|
||||||
|
delete state.boards[`${deletedBoardId}.assets`];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Image removed from board; i.e. Board reset for image
|
||||||
|
*/
|
||||||
|
builder.addMatcher(
|
||||||
|
boardImagesApi.endpoints.deleteBoardImage.matchFulfilled,
|
||||||
|
(state, action) => {
|
||||||
|
const { image_name } = action.meta.arg.originalArgs;
|
||||||
|
// remove from all user boards (skip all, none, batch)
|
||||||
|
const userBoards = selectUserBoards(state);
|
||||||
|
userBoards.forEach((board) => {
|
||||||
|
board.imageNames = board.imageNames.filter((n) => n !== image_name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Many images removed from board; i.e. Board reset for many images
|
||||||
|
*/
|
||||||
|
builder.addMatcher(
|
||||||
|
boardImagesApi.endpoints.deleteManyBoardImages.matchFulfilled,
|
||||||
|
(state, action) => {
|
||||||
|
const { image_names } = action.meta.arg.originalArgs;
|
||||||
|
// remove images from all boards
|
||||||
|
forEach(state.imageNamesByIdAndView, (board) => {
|
||||||
|
// only update the current view
|
||||||
|
const view = board[state.galleryView];
|
||||||
|
view.imageNames = view.imageNames.filter((n) =>
|
||||||
|
image_names.includes(n)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -201,14 +410,10 @@ export const {
|
|||||||
selectEntities: selectImagesEntities,
|
selectEntities: selectImagesEntities,
|
||||||
selectIds: selectImagesIds,
|
selectIds: selectImagesIds,
|
||||||
selectTotal: selectImagesTotal,
|
selectTotal: selectImagesTotal,
|
||||||
} = imagesAdapter.getSelectors<RootState>((state) => state.gallery);
|
} = galleryImagesAdapter.getSelectors<RootState>((state) => state.gallery);
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
imageUpserted,
|
|
||||||
imageUpdatedOne,
|
|
||||||
imageRemoved,
|
|
||||||
imagesRemoved,
|
imagesRemoved,
|
||||||
imageCategoriesChanged,
|
|
||||||
imageRangeEndSelected,
|
imageRangeEndSelected,
|
||||||
imageSelectionToggled,
|
imageSelectionToggled,
|
||||||
imageSelected,
|
imageSelected,
|
||||||
@ -221,44 +426,12 @@ export const {
|
|||||||
|
|
||||||
export default gallerySlice.reducer;
|
export default gallerySlice.reducer;
|
||||||
|
|
||||||
export const selectFilteredImagesLocal = createSelector(
|
const selectUserBoards = (state: typeof initialGalleryState) =>
|
||||||
(state: typeof initialGalleryState) => state,
|
filter(state.boards, (board, path) => !systemBoards.includes(path));
|
||||||
(galleryState) => {
|
|
||||||
const allImages = imagesAdapter.getSelectors().selectAll(galleryState);
|
|
||||||
const { categories, selectedBoardId } = galleryState;
|
|
||||||
|
|
||||||
const filteredImages = allImages.filter((i) => {
|
const selectCurrentBoard = (state: typeof initialGalleryState) =>
|
||||||
const isInCategory = categories.includes(i.image_category);
|
state.boards[`${state.selectedBoardId}.${state.galleryView}`];
|
||||||
const isInSelectedBoard = selectedBoardId
|
|
||||||
? i.board_id === selectedBoardId
|
|
||||||
: true;
|
|
||||||
return isInCategory && isInSelectedBoard;
|
|
||||||
});
|
|
||||||
|
|
||||||
return filteredImages;
|
const isImagesView = (board: BoardPath) => board.split('.')[1] === 'images';
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFilteredImages = createSelector(
|
const isAssetsView = (board: BoardPath) => board.split('.')[1] === 'assets';
|
||||||
(state: RootState) => state,
|
|
||||||
(state) => {
|
|
||||||
return selectFilteredImagesLocal(state.gallery);
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFilteredImagesAsObject = createSelector(
|
|
||||||
selectFilteredImages,
|
|
||||||
(filteredImages) => keyBy(filteredImages, 'image_name')
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectFilteredImagesIds = createSelector(
|
|
||||||
selectFilteredImages,
|
|
||||||
(filteredImages) => filteredImages.map((i) => i.image_name)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectLastSelectedImage = createSelector(
|
|
||||||
(state: RootState) => state,
|
|
||||||
(state) => state.gallery.selection[state.gallery.selection.length - 1],
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
||||||
|
@ -121,8 +121,9 @@ const nodesSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
state.invocationTemplates = action.payload;
|
state.invocationTemplates = action.payload;
|
||||||
},
|
},
|
||||||
nodeEditorReset: () => {
|
nodeEditorReset: (state) => {
|
||||||
return { ...initialNodesState };
|
state.nodes = [];
|
||||||
|
state.edges = [];
|
||||||
},
|
},
|
||||||
setEditorInstance: (state, action) => {
|
setEditorInstance: (state, action) => {
|
||||||
state.editorInstance = action.payload;
|
state.editorInstance = action.payload;
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import { Flex, Spacer, Text } from '@chakra-ui/react';
|
import { Flex, Spacer, Text } from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
|
||||||
import { useCallback, useMemo } from 'react';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
|
||||||
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
import { skipToken } from '@reduxjs/toolkit/dist/query';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
|
||||||
import { FaLayerGroup, FaUndo, FaUpload } from 'react-icons/fa';
|
|
||||||
import useImageUploader from 'common/hooks/useImageUploader';
|
|
||||||
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
|
||||||
import IAIButton from 'common/components/IAIButton';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
|
import IAIButton from 'common/components/IAIButton';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
|
||||||
|
import useImageUploader from 'common/hooks/useImageUploader';
|
||||||
|
import BatchImageContainer from 'features/batch/components/BatchImageContainer';
|
||||||
import {
|
import {
|
||||||
asInitialImageToggled,
|
asInitialImageToggled,
|
||||||
batchReset,
|
batchReset,
|
||||||
} from 'features/batch/store/batchSlice';
|
} from 'features/batch/store/batchSlice';
|
||||||
import BatchImageContainer from 'features/batch/components/BatchImageContainer';
|
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { FaLayerGroup, FaUndo, FaUpload } from 'react-icons/fa';
|
||||||
|
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||||
import { PostUploadAction } from 'services/api/thunks/image';
|
import { PostUploadAction } from 'services/api/thunks/image';
|
||||||
import InitialImage from './InitialImage';
|
import InitialImage from './InitialImage';
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ const InitialImageDisplay = () => {
|
|||||||
Initial Image
|
Initial Image
|
||||||
</Text>
|
</Text>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{/* <IAIButton
|
<IAIButton
|
||||||
tooltip={useBatchAsInitialImage ? 'Disable Batch' : 'Enable Batch'}
|
tooltip={useBatchAsInitialImage ? 'Disable Batch' : 'Enable Batch'}
|
||||||
aria-label={useBatchAsInitialImage ? 'Disable Batch' : 'Enable Batch'}
|
aria-label={useBatchAsInitialImage ? 'Disable Batch' : 'Enable Batch'}
|
||||||
leftIcon={<FaLayerGroup />}
|
leftIcon={<FaLayerGroup />}
|
||||||
@ -122,7 +122,7 @@ const InitialImageDisplay = () => {
|
|||||||
onClick={handleClickUseBatch}
|
onClick={handleClickUseBatch}
|
||||||
>
|
>
|
||||||
{useBatchAsInitialImage ? 'Batch' : 'Single'}
|
{useBatchAsInitialImage ? 'Batch' : 'Single'}
|
||||||
</IAIButton> */}
|
</IAIButton>
|
||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
tooltip={
|
tooltip={
|
||||||
useBatchAsInitialImage ? 'Upload to Batch' : 'Upload Initial Image'
|
useBatchAsInitialImage ? 'Upload to Batch' : 'Upload Initial Image'
|
||||||
@ -146,8 +146,7 @@ const InitialImageDisplay = () => {
|
|||||||
isDisabled={isResetButtonDisabled}
|
isDisabled={isResetButtonDisabled}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<InitialImage />
|
{useBatchAsInitialImage ? <BatchImageContainer /> : <InitialImage />}
|
||||||
{/* {useBatchAsInitialImage ? <BatchImageContainer /> : <InitialImage />} */}
|
|
||||||
<input {...getUploadInputProps()} />
|
<input {...getUploadInputProps()} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ import { isEqual } from 'lodash-es';
|
|||||||
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaCube, FaFont, FaImage } from 'react-icons/fa';
|
import { FaCube, FaFont, FaImage, FaLayerGroup } from 'react-icons/fa';
|
||||||
import { MdDeviceHub, MdGridOn } from 'react-icons/md';
|
import { MdDeviceHub, MdGridOn } from 'react-icons/md';
|
||||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||||
import { useMinimumPanelSize } from '../hooks/useMinimumPanelSize';
|
import { useMinimumPanelSize } from '../hooks/useMinimumPanelSize';
|
||||||
@ -32,6 +32,7 @@ import {
|
|||||||
activeTabIndexSelector,
|
activeTabIndexSelector,
|
||||||
activeTabNameSelector,
|
activeTabNameSelector,
|
||||||
} from '../store/uiSelectors';
|
} from '../store/uiSelectors';
|
||||||
|
import BatchTab from './tabs/Batch/BatchTab';
|
||||||
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
|
import ImageTab from './tabs/ImageToImage/ImageToImageTab';
|
||||||
import ModelManagerTab from './tabs/ModelManager/ModelManagerTab';
|
import ModelManagerTab from './tabs/ModelManager/ModelManagerTab';
|
||||||
import NodesTab from './tabs/Nodes/NodesTab';
|
import NodesTab from './tabs/Nodes/NodesTab';
|
||||||
@ -78,11 +79,12 @@ const tabs: InvokeTabInfo[] = [
|
|||||||
icon: <Icon as={FaCube} sx={{ boxSize: 6, pointerEvents: 'none' }} />,
|
icon: <Icon as={FaCube} sx={{ boxSize: 6, pointerEvents: 'none' }} />,
|
||||||
content: <ModelManagerTab />,
|
content: <ModelManagerTab />,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// id: 'batch',
|
id: 'batch',
|
||||||
// icon: <Icon as={FaLayerGroup} sx={{ boxSize: 6, pointerEvents: 'none' }} />,
|
translationKey: 'modelManager.modelManager',
|
||||||
// content: <BatchTab />,
|
icon: <Icon as={FaLayerGroup} sx={{ boxSize: 6, pointerEvents: 'none' }} />,
|
||||||
// },
|
content: <BatchTab />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const enabledTabsSelector = createSelector(
|
const enabledTabsSelector = createSelector(
|
||||||
|
@ -1,44 +1,57 @@
|
|||||||
import { OffsetPaginatedResults_ImageDTO_ } from 'services/api/types';
|
import { PatchCollection } from '@reduxjs/toolkit/dist/query/core/buildThunks';
|
||||||
import { api } from '..';
|
import { ApiFullTagDescription, LIST_TAG, api } from '..';
|
||||||
import { paths } from '../schema';
|
import { components, paths } from '../schema';
|
||||||
import { imagesApi } from './images';
|
import { imagesApi } from './images';
|
||||||
|
|
||||||
type ListBoardImagesArg =
|
|
||||||
paths['/api/v1/board_images/{board_id}']['get']['parameters']['path'] &
|
|
||||||
paths['/api/v1/board_images/{board_id}']['get']['parameters']['query'];
|
|
||||||
|
|
||||||
type AddImageToBoardArg =
|
type AddImageToBoardArg =
|
||||||
paths['/api/v1/board_images/']['post']['requestBody']['content']['application/json'];
|
paths['/api/v1/board_images/{board_id}']['post']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
type AddManyImagesToBoardArg =
|
||||||
|
paths['/api/v1/board_images/{board_id}/images']['patch']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
type RemoveImageFromBoardArg =
|
type RemoveImageFromBoardArg =
|
||||||
paths['/api/v1/board_images/']['delete']['requestBody']['content']['application/json'];
|
paths['/api/v1/board_images/']['delete']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
type RemoveManyBoardImagesArg =
|
||||||
|
paths['/api/v1/board_images/images']['post']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
type GetAllBoardImagesForBoardResult =
|
||||||
|
components['schemas']['GetAllBoardImagesForBoardResult'];
|
||||||
|
|
||||||
export const boardImagesApi = api.injectEndpoints({
|
export const boardImagesApi = api.injectEndpoints({
|
||||||
endpoints: (build) => ({
|
endpoints: (build) => ({
|
||||||
/**
|
/**
|
||||||
* Board Images Queries
|
* Board Images Queries
|
||||||
*/
|
*/
|
||||||
|
|
||||||
listBoardImages: build.query<
|
getAllBoardImagesForBoard: build.query<
|
||||||
OffsetPaginatedResults_ImageDTO_,
|
GetAllBoardImagesForBoardResult,
|
||||||
ListBoardImagesArg
|
{ board_id: string }
|
||||||
>({
|
>({
|
||||||
query: ({ board_id, offset, limit }) => ({
|
query: ({ board_id }) => ({
|
||||||
url: `board_images/${board_id}`,
|
url: `board_images/${board_id}`,
|
||||||
method: 'DELETE',
|
method: 'GET',
|
||||||
body: { offset, limit },
|
|
||||||
}),
|
}),
|
||||||
|
providesTags: (result, error, arg) => [
|
||||||
|
{
|
||||||
|
type: 'Board',
|
||||||
|
id: arg.board_id,
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Board Images Mutations
|
* Board Images Mutations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
addImageToBoard: build.mutation<void, AddImageToBoardArg>({
|
addBoardImage: build.mutation<
|
||||||
|
void,
|
||||||
|
{ board_id: string; image_name: string }
|
||||||
|
>({
|
||||||
query: ({ board_id, image_name }) => ({
|
query: ({ board_id, image_name }) => ({
|
||||||
url: `board_images/`,
|
url: `board_images/${board_id}`,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: { board_id, image_name },
|
body: image_name,
|
||||||
}),
|
}),
|
||||||
invalidatesTags: (result, error, arg) => [
|
invalidatesTags: (result, error, arg) => [
|
||||||
{ type: 'Board', id: arg.board_id },
|
{ type: 'Board', id: arg.board_id },
|
||||||
@ -60,19 +73,55 @@ export const boardImagesApi = api.injectEndpoints({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
removeImageFromBoard: build.mutation<void, RemoveImageFromBoardArg>({
|
addManyBoardImages: build.mutation<
|
||||||
query: ({ board_id, image_name }) => ({
|
string[],
|
||||||
url: `board_images/`,
|
{ board_id: string; image_names: string[] }
|
||||||
method: 'DELETE',
|
>({
|
||||||
body: { board_id, image_name },
|
query: ({ board_id, image_names }) => ({
|
||||||
|
url: `board_images/${board_id}/images`,
|
||||||
|
method: 'PATCH',
|
||||||
|
body: image_names,
|
||||||
}),
|
}),
|
||||||
invalidatesTags: (result, error, arg) => [
|
invalidatesTags: (result, error, arg) => {
|
||||||
{ type: 'Board', id: arg.board_id },
|
const tags: ApiFullTagDescription[] = [
|
||||||
],
|
{ type: 'Board', id: arg.board_id },
|
||||||
|
{ type: 'Board', id: LIST_TAG },
|
||||||
|
];
|
||||||
|
return tags;
|
||||||
|
},
|
||||||
async onQueryStarted(
|
async onQueryStarted(
|
||||||
{ image_name, ...patch },
|
{ image_names, board_id },
|
||||||
{ dispatch, queryFulfilled }
|
{ dispatch, queryFulfilled }
|
||||||
) {
|
) {
|
||||||
|
const patches: PatchCollection[] = [];
|
||||||
|
|
||||||
|
image_names.forEach((n) => {
|
||||||
|
const patchResult = dispatch(
|
||||||
|
imagesApi.util.updateQueryData('getImageDTO', n, (draft) => {
|
||||||
|
Object.assign(draft, { board_id });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
patches.push(patchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryFulfilled;
|
||||||
|
} catch {
|
||||||
|
patches.forEach((p) => p.undo());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
deleteBoardImage: build.mutation<void, { image_name: string }>({
|
||||||
|
query: ({ image_name }) => ({
|
||||||
|
url: `board_images/`,
|
||||||
|
method: 'DELETE',
|
||||||
|
body: image_name,
|
||||||
|
}),
|
||||||
|
invalidatesTags: (result, error, arg) => [
|
||||||
|
{ type: 'Board', id: LIST_TAG },
|
||||||
|
],
|
||||||
|
async onQueryStarted({ image_name }, { dispatch, queryFulfilled }) {
|
||||||
const patchResult = dispatch(
|
const patchResult = dispatch(
|
||||||
imagesApi.util.updateQueryData('getImageDTO', image_name, (draft) => {
|
imagesApi.util.updateQueryData('getImageDTO', image_name, (draft) => {
|
||||||
Object.assign(draft, { board_id: null });
|
Object.assign(draft, { board_id: null });
|
||||||
@ -85,11 +134,42 @@ export const boardImagesApi = api.injectEndpoints({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
deleteManyBoardImages: build.mutation<void, { image_names: string[] }>({
|
||||||
|
query: ({ image_names }) => ({
|
||||||
|
url: `board_images/images`,
|
||||||
|
method: 'POST',
|
||||||
|
body: image_names,
|
||||||
|
}),
|
||||||
|
invalidatesTags: (result, error, arg) => [
|
||||||
|
{ type: 'Board', id: LIST_TAG },
|
||||||
|
],
|
||||||
|
async onQueryStarted({ image_names }, { dispatch, queryFulfilled }) {
|
||||||
|
const patches: PatchCollection[] = [];
|
||||||
|
|
||||||
|
image_names.forEach((n) => {
|
||||||
|
const patchResult = dispatch(
|
||||||
|
imagesApi.util.updateQueryData('getImageDTO', n, (draft) => {
|
||||||
|
Object.assign(draft, { board_id: null });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
patches.push(patchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await queryFulfilled;
|
||||||
|
} catch {
|
||||||
|
patches.forEach((p) => p.undo());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
useAddImageToBoardMutation,
|
useGetAllBoardImagesForBoardQuery,
|
||||||
useRemoveImageFromBoardMutation,
|
useAddBoardImageMutation,
|
||||||
useListBoardImagesQuery,
|
useAddManyBoardImagesMutation,
|
||||||
|
useDeleteBoardImageMutation,
|
||||||
|
useDeleteManyBoardImagesMutation,
|
||||||
} = boardImagesApi;
|
} = boardImagesApi;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BoardDTO, OffsetPaginatedResults_BoardDTO_ } from 'services/api/types';
|
import { BoardDTO, OffsetPaginatedResults_BoardDTO_ } from 'services/api/types';
|
||||||
import { ApiFullTagDescription, LIST_TAG, api } from '..';
|
import { ApiFullTagDescription, LIST_TAG, api } from '..';
|
||||||
import { paths } from '../schema';
|
import { components, paths } from '../schema';
|
||||||
|
|
||||||
type ListBoardsArg = NonNullable<
|
type ListBoardsArg = NonNullable<
|
||||||
paths['/api/v1/boards/']['get']['parameters']['query']
|
paths['/api/v1/boards/']['get']['parameters']['query']
|
||||||
@ -20,7 +20,7 @@ export const boardsApi = api.injectEndpoints({
|
|||||||
query: (arg) => ({ url: 'boards/', params: arg }),
|
query: (arg) => ({ url: 'boards/', params: arg }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
// any list of boards
|
// any list of boards
|
||||||
const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST_TAG }];
|
const tags: ApiFullTagDescription[] = [{ type: 'Board', id: LIST_TAG }];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
// and individual tags for each board
|
// and individual tags for each board
|
||||||
@ -43,7 +43,7 @@ export const boardsApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
// any list of boards
|
// any list of boards
|
||||||
const tags: ApiFullTagDescription[] = [{ id: 'Board', type: LIST_TAG }];
|
const tags: ApiFullTagDescription[] = [{ type: 'Board', id: LIST_TAG }];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
// and individual tags for each board
|
// and individual tags for each board
|
||||||
@ -69,7 +69,7 @@ export const boardsApi = api.injectEndpoints({
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
params: { board_name },
|
params: { board_name },
|
||||||
}),
|
}),
|
||||||
invalidatesTags: [{ id: 'Board', type: LIST_TAG }],
|
invalidatesTags: [{ type: 'Board', id: LIST_TAG }],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateBoard: build.mutation<BoardDTO, UpdateBoardArg>({
|
updateBoard: build.mutation<BoardDTO, UpdateBoardArg>({
|
||||||
@ -86,9 +86,19 @@ export const boardsApi = api.injectEndpoints({
|
|||||||
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>({
|
deleteBoardAndImages: build.mutation<
|
||||||
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE', params: { include_images: true } }),
|
components['schemas']['DeleteManyImagesResult'],
|
||||||
invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }, { type: 'Image', id: LIST_TAG }],
|
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 },
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@ -99,5 +109,5 @@ export const {
|
|||||||
useCreateBoardMutation,
|
useCreateBoardMutation,
|
||||||
useUpdateBoardMutation,
|
useUpdateBoardMutation,
|
||||||
useDeleteBoardMutation,
|
useDeleteBoardMutation,
|
||||||
useDeleteBoardAndImagesMutation
|
useDeleteBoardAndImagesMutation,
|
||||||
} = boardsApi;
|
} = boardsApi;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ApiFullTagDescription, api } from '..';
|
import { api } from '..';
|
||||||
import { ImageDTO } from '../types';
|
import { ImageDTO } from '../types';
|
||||||
|
|
||||||
export const imagesApi = api.injectEndpoints({
|
export const imagesApi = api.injectEndpoints({
|
||||||
@ -7,14 +7,8 @@ export const imagesApi = api.injectEndpoints({
|
|||||||
* Image Queries
|
* Image Queries
|
||||||
*/
|
*/
|
||||||
getImageDTO: build.query<ImageDTO, string>({
|
getImageDTO: build.query<ImageDTO, string>({
|
||||||
query: (image_name) => ({ url: `images/${image_name}/metadata` }),
|
query: (image_name) => ({ url: `images/${image_name}` }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => [{ type: 'Image', id: arg }],
|
||||||
const tags: ApiFullTagDescription[] = [{ type: 'Image', id: arg }];
|
|
||||||
if (result?.board_id) {
|
|
||||||
tags.push({ type: 'Board', id: result.board_id });
|
|
||||||
}
|
|
||||||
return tags;
|
|
||||||
},
|
|
||||||
keepUnusedDataFor: 86400, // 24 hours
|
keepUnusedDataFor: 86400, // 24 hours
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
@ -73,7 +73,7 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
query: () => ({ url: 'models/', params: { model_type: 'main' } }),
|
query: () => ({ url: 'models/', params: { model_type: 'main' } }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
const tags: ApiFullTagDescription[] = [
|
const tags: ApiFullTagDescription[] = [
|
||||||
{ id: 'MainModel', type: LIST_TAG },
|
{ type: 'MainModel', id: LIST_TAG },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -105,7 +105,7 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
query: () => ({ url: 'models/', params: { model_type: 'lora' } }),
|
query: () => ({ url: 'models/', params: { model_type: 'lora' } }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
const tags: ApiFullTagDescription[] = [
|
const tags: ApiFullTagDescription[] = [
|
||||||
{ id: 'LoRAModel', type: LIST_TAG },
|
{ type: 'LoRAModel', id: LIST_TAG },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -140,7 +140,7 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
query: () => ({ url: 'models/', params: { model_type: 'controlnet' } }),
|
query: () => ({ url: 'models/', params: { model_type: 'controlnet' } }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
const tags: ApiFullTagDescription[] = [
|
const tags: ApiFullTagDescription[] = [
|
||||||
{ id: 'ControlNetModel', type: LIST_TAG },
|
{ type: 'ControlNetModel', id: LIST_TAG },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -172,7 +172,7 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
query: () => ({ url: 'models/', params: { model_type: 'vae' } }),
|
query: () => ({ url: 'models/', params: { model_type: 'vae' } }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
const tags: ApiFullTagDescription[] = [
|
const tags: ApiFullTagDescription[] = [
|
||||||
{ id: 'VaeModel', type: LIST_TAG },
|
{ type: 'VaeModel', id: LIST_TAG },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -207,7 +207,7 @@ export const modelsApi = api.injectEndpoints({
|
|||||||
query: () => ({ url: 'models/', params: { model_type: 'embedding' } }),
|
query: () => ({ url: 'models/', params: { model_type: 'embedding' } }),
|
||||||
providesTags: (result, error, arg) => {
|
providesTags: (result, error, arg) => {
|
||||||
const tags: ApiFullTagDescription[] = [
|
const tags: ApiFullTagDescription[] = [
|
||||||
{ id: 'TextualInversionModel', type: LIST_TAG },
|
{ type: 'TextualInversionModel', id: LIST_TAG },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
384
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
384
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
@ -107,12 +107,7 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
put: operations["merge_models"];
|
put: operations["merge_models"];
|
||||||
};
|
};
|
||||||
"/api/v1/images/": {
|
"/api/v1/images/upload": {
|
||||||
/**
|
|
||||||
* List Images With Metadata
|
|
||||||
* @description Gets a list of images
|
|
||||||
*/
|
|
||||||
get: operations["list_images_with_metadata"];
|
|
||||||
/**
|
/**
|
||||||
* Upload Image
|
* Upload Image
|
||||||
* @description Uploads an image
|
* @description Uploads an image
|
||||||
@ -121,10 +116,10 @@ export type paths = {
|
|||||||
};
|
};
|
||||||
"/api/v1/images/{image_name}": {
|
"/api/v1/images/{image_name}": {
|
||||||
/**
|
/**
|
||||||
* Get Image Full
|
* Get Image Dto
|
||||||
* @description Gets a full-resolution image file
|
* @description Gets an image's DTO
|
||||||
*/
|
*/
|
||||||
get: operations["get_image_full"];
|
get: operations["get_image"];
|
||||||
/**
|
/**
|
||||||
* Delete Image
|
* Delete Image
|
||||||
* @description Deletes an image
|
* @description Deletes an image
|
||||||
@ -136,12 +131,12 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
patch: operations["update_image"];
|
patch: operations["update_image"];
|
||||||
};
|
};
|
||||||
"/api/v1/images/{image_name}/metadata": {
|
"/api/v1/images/{image_name}/full_size": {
|
||||||
/**
|
/**
|
||||||
* Get Image Metadata
|
* Get Image Full Size
|
||||||
* @description Gets an image's metadata
|
* @description Gets a full-resolution image file
|
||||||
*/
|
*/
|
||||||
get: operations["get_image_metadata"];
|
get: operations["get_image_full_size"];
|
||||||
};
|
};
|
||||||
"/api/v1/images/{image_name}/thumbnail": {
|
"/api/v1/images/{image_name}/thumbnail": {
|
||||||
/**
|
/**
|
||||||
@ -157,6 +152,25 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
get: operations["get_image_urls"];
|
get: operations["get_image_urls"];
|
||||||
};
|
};
|
||||||
|
"/api/v1/images/": {
|
||||||
|
/**
|
||||||
|
* Get Many Images
|
||||||
|
* @description Gets a list of images
|
||||||
|
*/
|
||||||
|
get: operations["get_many_images"];
|
||||||
|
/**
|
||||||
|
* Get Images By Names
|
||||||
|
* @description Gets a list of images
|
||||||
|
*/
|
||||||
|
post: operations["get_images_by_names"];
|
||||||
|
};
|
||||||
|
"/api/v1/images/delete": {
|
||||||
|
/**
|
||||||
|
* Delete Many Images
|
||||||
|
* @description Deletes many images
|
||||||
|
*/
|
||||||
|
post: operations["delete_many_images"];
|
||||||
|
};
|
||||||
"/api/v1/boards/": {
|
"/api/v1/boards/": {
|
||||||
/**
|
/**
|
||||||
* List Boards
|
* List Boards
|
||||||
@ -186,24 +200,38 @@ export type paths = {
|
|||||||
*/
|
*/
|
||||||
patch: operations["update_board"];
|
patch: operations["update_board"];
|
||||||
};
|
};
|
||||||
"/api/v1/board_images/": {
|
"/api/v1/board_images/{board_id}": {
|
||||||
|
/**
|
||||||
|
* Get All Board Images For Board
|
||||||
|
* @description Gets all image names for a board
|
||||||
|
*/
|
||||||
|
get: operations["get_all_board_images_for_board"];
|
||||||
/**
|
/**
|
||||||
* Create Board Image
|
* Create Board Image
|
||||||
* @description Creates a board_image
|
* @description Creates a board_image
|
||||||
*/
|
*/
|
||||||
post: operations["create_board_image"];
|
post: operations["create_board_image"];
|
||||||
|
};
|
||||||
|
"/api/v1/board_images/": {
|
||||||
/**
|
/**
|
||||||
* Remove Board Image
|
* Remove Board Image
|
||||||
* @description Deletes a board_image
|
* @description Deletes a board_image
|
||||||
*/
|
*/
|
||||||
delete: operations["remove_board_image"];
|
delete: operations["remove_board_image"];
|
||||||
};
|
};
|
||||||
"/api/v1/board_images/{board_id}": {
|
"/api/v1/board_images/{board_id}/images": {
|
||||||
/**
|
/**
|
||||||
* List Board Images
|
* Create Multiple Board Images
|
||||||
* @description Gets a list of images for a board
|
* @description Add many images to a board
|
||||||
*/
|
*/
|
||||||
get: operations["list_board_images"];
|
patch: operations["create_multiple_board_images"];
|
||||||
|
};
|
||||||
|
"/api/v1/board_images/images": {
|
||||||
|
/**
|
||||||
|
* Delete Multiple Board Images
|
||||||
|
* @description Remove many images from their boards, if they have one
|
||||||
|
*/
|
||||||
|
post: operations["delete_multiple_board_images"];
|
||||||
};
|
};
|
||||||
"/api/v1/app/version": {
|
"/api/v1/app/version": {
|
||||||
/** Get Version */
|
/** Get Version */
|
||||||
@ -318,19 +346,6 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
image_count: number;
|
image_count: number;
|
||||||
};
|
};
|
||||||
/** Body_create_board_image */
|
|
||||||
Body_create_board_image: {
|
|
||||||
/**
|
|
||||||
* Board Id
|
|
||||||
* @description The id of the board to add to
|
|
||||||
*/
|
|
||||||
board_id: string;
|
|
||||||
/**
|
|
||||||
* Image Name
|
|
||||||
* @description The name of the image to add
|
|
||||||
*/
|
|
||||||
image_name: string;
|
|
||||||
};
|
|
||||||
/** Body_import_model */
|
/** Body_import_model */
|
||||||
Body_import_model: {
|
Body_import_model: {
|
||||||
/**
|
/**
|
||||||
@ -373,19 +388,6 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
};
|
};
|
||||||
/** Body_remove_board_image */
|
|
||||||
Body_remove_board_image: {
|
|
||||||
/**
|
|
||||||
* Board Id
|
|
||||||
* @description The id of the board
|
|
||||||
*/
|
|
||||||
board_id: string;
|
|
||||||
/**
|
|
||||||
* Image Name
|
|
||||||
* @description The name of the image to remove
|
|
||||||
*/
|
|
||||||
image_name: string;
|
|
||||||
};
|
|
||||||
/** Body_upload_image */
|
/** Body_upload_image */
|
||||||
Body_upload_image: {
|
Body_upload_image: {
|
||||||
/**
|
/**
|
||||||
@ -869,6 +871,17 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
mask?: components["schemas"]["ImageField"];
|
mask?: components["schemas"]["ImageField"];
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* DeleteManyImagesResult
|
||||||
|
* @description The result of a delete many image operation.
|
||||||
|
*/
|
||||||
|
DeleteManyImagesResult: {
|
||||||
|
/**
|
||||||
|
* Deleted Images
|
||||||
|
* @description The names of the images that were successfully deleted
|
||||||
|
*/
|
||||||
|
deleted_images: (string)[];
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* DivideInvocation
|
* DivideInvocation
|
||||||
* @description Divides two numbers
|
* @description Divides two numbers
|
||||||
@ -1046,6 +1059,33 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
param?: number;
|
param?: number;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* GetAllBoardImagesForBoardResult
|
||||||
|
* @description The result of a get all image names for board operation.
|
||||||
|
*/
|
||||||
|
GetAllBoardImagesForBoardResult: {
|
||||||
|
/**
|
||||||
|
* Board Id
|
||||||
|
* @description The id of the board with which the images are associated
|
||||||
|
*/
|
||||||
|
board_id: string;
|
||||||
|
/**
|
||||||
|
* Image Names
|
||||||
|
* @description The names of the images that are associated with the board
|
||||||
|
*/
|
||||||
|
image_names: (string)[];
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* GetImagesByNamesResult
|
||||||
|
* @description The result of a get all image names for board operation.
|
||||||
|
*/
|
||||||
|
GetImagesByNamesResult: {
|
||||||
|
/**
|
||||||
|
* Image Dtos
|
||||||
|
* @description The names of the images that are associated with the board
|
||||||
|
*/
|
||||||
|
image_dtos: (components["schemas"]["ImageDTO"])[];
|
||||||
|
};
|
||||||
/** Graph */
|
/** Graph */
|
||||||
Graph: {
|
Graph: {
|
||||||
/**
|
/**
|
||||||
@ -1058,7 +1098,7 @@ export type components = {
|
|||||||
* @description The nodes in this graph
|
* @description The nodes in this graph
|
||||||
*/
|
*/
|
||||||
nodes?: {
|
nodes?: {
|
||||||
[key: string]: (components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined;
|
[key: string]: (components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"]) | undefined;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Edges
|
* Edges
|
||||||
@ -1101,7 +1141,7 @@ export type components = {
|
|||||||
* @description The results of node executions
|
* @description The results of node executions
|
||||||
*/
|
*/
|
||||||
results: {
|
results: {
|
||||||
[key: string]: (components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined;
|
[key: string]: (components["schemas"]["ImageOutput"] | components["schemas"]["MaskOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["LoraLoaderOutput"] | components["schemas"]["VaeLoaderOutput"] | components["schemas"]["PromptOutput"] | components["schemas"]["PromptCollectionOutput"] | components["schemas"]["CompelOutput"] | components["schemas"]["ClipSkipInvocationOutput"] | components["schemas"]["IntOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["IntCollectionOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["GraphInvocationOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"]) | undefined;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Errors
|
* Errors
|
||||||
@ -4425,18 +4465,18 @@ export type components = {
|
|||||||
*/
|
*/
|
||||||
image?: components["schemas"]["ImageField"];
|
image?: components["schemas"]["ImageField"];
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* StableDiffusion2ModelFormat
|
|
||||||
* @description An enumeration.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
|
||||||
/**
|
/**
|
||||||
* StableDiffusion1ModelFormat
|
* StableDiffusion1ModelFormat
|
||||||
* @description An enumeration.
|
* @description An enumeration.
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
|
StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
|
||||||
|
/**
|
||||||
|
* StableDiffusion2ModelFormat
|
||||||
|
* @description An enumeration.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||||
};
|
};
|
||||||
responses: never;
|
responses: never;
|
||||||
parameters: never;
|
parameters: never;
|
||||||
@ -4547,7 +4587,7 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
"application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
@ -4584,7 +4624,7 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
"application/json": components["schemas"]["LoadImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["ImageProcessorInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["LoraLoaderInvocation"] | components["schemas"]["VaeLoaderInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ClipSkipInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ParamIntInvocation"] | components["schemas"]["ParamFloatInvocation"] | components["schemas"]["TextToLatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UpscaleInvocation"] | components["schemas"]["RestoreFaceInvocation"] | components["schemas"]["InpaintInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["GraphInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["OpenposeImageProcessorInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["LatentsToLatentsInvocation"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
@ -4976,42 +5016,6 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
|
||||||
* List Images With Metadata
|
|
||||||
* @description Gets a list of images
|
|
||||||
*/
|
|
||||||
list_images_with_metadata: {
|
|
||||||
parameters: {
|
|
||||||
query?: {
|
|
||||||
/** @description The origin of images to list */
|
|
||||||
image_origin?: components["schemas"]["ResourceOrigin"];
|
|
||||||
/** @description The categories of image to include */
|
|
||||||
categories?: (components["schemas"]["ImageCategory"])[];
|
|
||||||
/** @description Whether to list intermediate images */
|
|
||||||
is_intermediate?: boolean;
|
|
||||||
/** @description The board id to filter by */
|
|
||||||
board_id?: string;
|
|
||||||
/** @description The page offset */
|
|
||||||
offset?: number;
|
|
||||||
/** @description The number of images per page */
|
|
||||||
limit?: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description Successful Response */
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["OffsetPaginatedResults_ImageDTO_"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/** @description Validation Error */
|
|
||||||
422: {
|
|
||||||
content: {
|
|
||||||
"application/json": components["schemas"]["HTTPValidationError"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
/**
|
/**
|
||||||
* Upload Image
|
* Upload Image
|
||||||
* @description Uploads an image
|
* @description Uploads an image
|
||||||
@ -5050,25 +5054,23 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Get Image Full
|
* Get Image Dto
|
||||||
* @description Gets a full-resolution image file
|
* @description Gets an image's DTO
|
||||||
*/
|
*/
|
||||||
get_image_full: {
|
get_image: {
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
/** @description The name of full-resolution image file to get */
|
/** @description The name of image to get */
|
||||||
image_name: string;
|
image_name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Return the full-resolution image */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"image/png": unknown;
|
"application/json": components["schemas"]["ImageDTO"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Image not found */
|
|
||||||
404: never;
|
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
422: {
|
422: {
|
||||||
content: {
|
content: {
|
||||||
@ -5135,23 +5137,25 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Get Image Metadata
|
* Get Image Full Size
|
||||||
* @description Gets an image's metadata
|
* @description Gets a full-resolution image file
|
||||||
*/
|
*/
|
||||||
get_image_metadata: {
|
get_image_full_size: {
|
||||||
parameters: {
|
parameters: {
|
||||||
path: {
|
path: {
|
||||||
/** @description The name of image to get */
|
/** @description The name of full-resolution image file to get */
|
||||||
image_name: string;
|
image_name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Successful Response */
|
/** @description Return the full-resolution image */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["ImageDTO"];
|
"image/png": unknown;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/** @description Image not found */
|
||||||
|
404: never;
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
422: {
|
422: {
|
||||||
content: {
|
content: {
|
||||||
@ -5214,6 +5218,92 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Get Many Images
|
||||||
|
* @description Gets a list of images
|
||||||
|
*/
|
||||||
|
get_many_images: {
|
||||||
|
parameters: {
|
||||||
|
query?: {
|
||||||
|
/** @description The origin of images to list */
|
||||||
|
image_origin?: components["schemas"]["ResourceOrigin"];
|
||||||
|
/** @description The categories of image to include */
|
||||||
|
categories?: (components["schemas"]["ImageCategory"])[];
|
||||||
|
/** @description Whether to list intermediate images */
|
||||||
|
is_intermediate?: boolean;
|
||||||
|
/** @description The board id to filter by, provide 'none' for images without a board */
|
||||||
|
board_id?: string;
|
||||||
|
/** @description The page offset */
|
||||||
|
offset?: number;
|
||||||
|
/** @description The number of images per page */
|
||||||
|
limit?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["OffsetPaginatedResults_ImageDTO_"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Get Images By Names
|
||||||
|
* @description Gets a list of images
|
||||||
|
*/
|
||||||
|
get_images_by_names: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": (string)[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["GetImagesByNamesResult"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Delete Many Images
|
||||||
|
* @description Deletes many images
|
||||||
|
*/
|
||||||
|
delete_many_images: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": (string)[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["DeleteManyImagesResult"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* List Boards
|
* List Boards
|
||||||
* @description Gets a list of boards
|
* @description Gets a list of boards
|
||||||
@ -5315,7 +5405,7 @@ export type operations = {
|
|||||||
/** @description Successful Response */
|
/** @description Successful Response */
|
||||||
200: {
|
200: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": unknown;
|
"application/json": components["schemas"]["DeleteManyImagesResult"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
@ -5357,14 +5447,46 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Get All Board Images For Board
|
||||||
|
* @description Gets all image names for a board
|
||||||
|
*/
|
||||||
|
get_all_board_images_for_board: {
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
/** @description The id of the board */
|
||||||
|
board_id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Successful Response */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["GetAllBoardImagesForBoardResult"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Create Board Image
|
* Create Board Image
|
||||||
* @description Creates a board_image
|
* @description Creates a board_image
|
||||||
*/
|
*/
|
||||||
create_board_image: {
|
create_board_image: {
|
||||||
|
parameters: {
|
||||||
|
path: {
|
||||||
|
/** @description The id of the board to add to */
|
||||||
|
board_id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["Body_create_board_image"];
|
"application/json": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
@ -5389,7 +5511,7 @@ export type operations = {
|
|||||||
remove_board_image: {
|
remove_board_image: {
|
||||||
requestBody: {
|
requestBody: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["Body_remove_board_image"];
|
"application/json": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
@ -5408,27 +5530,51 @@ export type operations = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* List Board Images
|
* Create Multiple Board Images
|
||||||
* @description Gets a list of images for a board
|
* @description Add many images to a board
|
||||||
*/
|
*/
|
||||||
list_board_images: {
|
create_multiple_board_images: {
|
||||||
parameters: {
|
parameters: {
|
||||||
query?: {
|
|
||||||
/** @description The page offset */
|
|
||||||
offset?: number;
|
|
||||||
/** @description The number of boards per page */
|
|
||||||
limit?: number;
|
|
||||||
};
|
|
||||||
path: {
|
path: {
|
||||||
/** @description The id of the board */
|
/** @description The id of the board */
|
||||||
board_id: string;
|
board_id: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": (string)[];
|
||||||
|
};
|
||||||
|
};
|
||||||
responses: {
|
responses: {
|
||||||
/** @description Successful Response */
|
/** @description The images were added to the board successfully */
|
||||||
200: {
|
201: {
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["OffsetPaginatedResults_ImageDTO_"];
|
"application/json": unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Validation Error */
|
||||||
|
422: {
|
||||||
|
content: {
|
||||||
|
"application/json": components["schemas"]["HTTPValidationError"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Delete Multiple Board Images
|
||||||
|
* @description Remove many images from their boards, if they have one
|
||||||
|
*/
|
||||||
|
delete_multiple_board_images: {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
"application/json": (string)[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description The images were removed from their boards successfully */
|
||||||
|
201: {
|
||||||
|
content: {
|
||||||
|
"application/json": unknown;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
/** @description Validation Error */
|
/** @description Validation Error */
|
||||||
|
41
invokeai/frontend/web/src/services/api/thunks/boardImages.ts
Normal file
41
invokeai/frontend/web/src/services/api/thunks/boardImages.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { createAppAsyncThunk } from 'app/store/storeUtils';
|
||||||
|
import { $client } from '../client';
|
||||||
|
import { components } from '../schema';
|
||||||
|
|
||||||
|
type Arg = { board_id: string };
|
||||||
|
|
||||||
|
type GetAllBoardImagesForBoardResult =
|
||||||
|
components['schemas']['GetAllBoardImagesForBoardResult'];
|
||||||
|
|
||||||
|
type GetImageUrlsThunkConfig = {
|
||||||
|
rejectValue: {
|
||||||
|
arg: Arg;
|
||||||
|
error: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Thunk to get image URLs
|
||||||
|
*/
|
||||||
|
export const boardImageNamesReceived = createAppAsyncThunk<
|
||||||
|
GetAllBoardImagesForBoardResult,
|
||||||
|
Arg,
|
||||||
|
GetImageUrlsThunkConfig
|
||||||
|
>('thunkApi/boardImageNamesReceived', async (arg, { rejectWithValue }) => {
|
||||||
|
const { get } = $client.get();
|
||||||
|
const { data, error, response } = await get(
|
||||||
|
'/api/v1/board_images/{board_id}',
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
path: {
|
||||||
|
board_id: arg.board_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return rejectWithValue({ arg, error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
@ -1,9 +1,10 @@
|
|||||||
import queryString from 'query-string';
|
|
||||||
import { createAppAsyncThunk } from 'app/store/storeUtils';
|
import { createAppAsyncThunk } from 'app/store/storeUtils';
|
||||||
import { selectImagesAll } from 'features/gallery/store/gallerySlice';
|
import { selectImagesAll } from 'features/gallery/store/gallerySlice';
|
||||||
import { size } from 'lodash-es';
|
import { size } from 'lodash-es';
|
||||||
import { paths } from 'services/api/schema';
|
import queryString from 'query-string';
|
||||||
import { $client } from 'services/api/client';
|
import { $client } from 'services/api/client';
|
||||||
|
import { paths } from 'services/api/schema';
|
||||||
|
import { ImageCategory, OffsetPaginatedResults_ImageDTO_ } from '../types';
|
||||||
|
|
||||||
type GetImageUrlsArg =
|
type GetImageUrlsArg =
|
||||||
paths['/api/v1/images/{image_name}/urls']['get']['parameters']['path'];
|
paths['/api/v1/images/{image_name}/urls']['get']['parameters']['path'];
|
||||||
@ -24,7 +25,7 @@ export const imageUrlsReceived = createAppAsyncThunk<
|
|||||||
GetImageUrlsResponse,
|
GetImageUrlsResponse,
|
||||||
GetImageUrlsArg,
|
GetImageUrlsArg,
|
||||||
GetImageUrlsThunkConfig
|
GetImageUrlsThunkConfig
|
||||||
>('api/imageUrlsReceived', async (arg, { rejectWithValue }) => {
|
>('thunkApi/imageUrlsReceived', async (arg, { rejectWithValue }) => {
|
||||||
const { image_name } = arg;
|
const { image_name } = arg;
|
||||||
const { get } = $client.get();
|
const { get } = $client.get();
|
||||||
const { data, error, response } = await get(
|
const { data, error, response } = await get(
|
||||||
@ -45,34 +46,31 @@ export const imageUrlsReceived = createAppAsyncThunk<
|
|||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
|
|
||||||
type GetImageMetadataArg =
|
type GetImageDTOArg =
|
||||||
paths['/api/v1/images/{image_name}/metadata']['get']['parameters']['path'];
|
paths['/api/v1/images/{image_name}']['get']['parameters']['path'];
|
||||||
|
|
||||||
type GetImageMetadataResponse =
|
type GetImageDTOResponse =
|
||||||
paths['/api/v1/images/{image_name}/metadata']['get']['responses']['200']['content']['application/json'];
|
paths['/api/v1/images/{image_name}']['get']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
type GetImageMetadataThunkConfig = {
|
type GetImageDTOThunkConfig = {
|
||||||
rejectValue: {
|
rejectValue: {
|
||||||
arg: GetImageMetadataArg;
|
arg: GetImageDTOArg;
|
||||||
error: unknown;
|
error: unknown;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const imageMetadataReceived = createAppAsyncThunk<
|
export const imageDTOReceived = createAppAsyncThunk<
|
||||||
GetImageMetadataResponse,
|
GetImageDTOResponse,
|
||||||
GetImageMetadataArg,
|
GetImageDTOArg,
|
||||||
GetImageMetadataThunkConfig
|
GetImageDTOThunkConfig
|
||||||
>('api/imageMetadataReceived', async (arg, { rejectWithValue }) => {
|
>('thunkApi/imageDTOReceived', async (arg, { rejectWithValue }) => {
|
||||||
const { image_name } = arg;
|
const { image_name } = arg;
|
||||||
const { get } = $client.get();
|
const { get } = $client.get();
|
||||||
const { data, error, response } = await get(
|
const { data, error, response } = await get('/api/v1/images/{image_name}', {
|
||||||
'/api/v1/images/{image_name}/metadata',
|
params: {
|
||||||
{
|
path: { image_name },
|
||||||
params: {
|
},
|
||||||
path: { image_name },
|
});
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return rejectWithValue({ arg, error });
|
return rejectWithValue({ arg, error });
|
||||||
@ -127,13 +125,13 @@ export type PostUploadAction =
|
|||||||
| AddToBatchAction;
|
| AddToBatchAction;
|
||||||
|
|
||||||
type UploadImageArg =
|
type UploadImageArg =
|
||||||
paths['/api/v1/images/']['post']['parameters']['query'] & {
|
paths['/api/v1/images/upload']['post']['parameters']['query'] & {
|
||||||
file: File;
|
file: File;
|
||||||
postUploadAction?: PostUploadAction;
|
postUploadAction?: PostUploadAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UploadImageResponse =
|
type UploadImageResponse =
|
||||||
paths['/api/v1/images/']['post']['responses']['201']['content']['application/json'];
|
paths['/api/v1/images/upload']['post']['responses']['201']['content']['application/json'];
|
||||||
|
|
||||||
type UploadImageThunkConfig = {
|
type UploadImageThunkConfig = {
|
||||||
rejectValue: {
|
rejectValue: {
|
||||||
@ -148,7 +146,7 @@ export const imageUploaded = createAppAsyncThunk<
|
|||||||
UploadImageResponse,
|
UploadImageResponse,
|
||||||
UploadImageArg,
|
UploadImageArg,
|
||||||
UploadImageThunkConfig
|
UploadImageThunkConfig
|
||||||
>('api/imageUploaded', async (arg, { rejectWithValue }) => {
|
>('thunkApi/imageUploaded', async (arg, { rejectWithValue }) => {
|
||||||
const {
|
const {
|
||||||
postUploadAction,
|
postUploadAction,
|
||||||
file,
|
file,
|
||||||
@ -157,7 +155,7 @@ export const imageUploaded = createAppAsyncThunk<
|
|||||||
session_id,
|
session_id,
|
||||||
} = arg;
|
} = arg;
|
||||||
const { post } = $client.get();
|
const { post } = $client.get();
|
||||||
const { data, error, response } = await post('/api/v1/images/', {
|
const { data, error, response } = await post('/api/v1/images/upload', {
|
||||||
params: {
|
params: {
|
||||||
query: {
|
query: {
|
||||||
image_category,
|
image_category,
|
||||||
@ -199,7 +197,7 @@ export const imageDeleted = createAppAsyncThunk<
|
|||||||
DeleteImageResponse,
|
DeleteImageResponse,
|
||||||
DeleteImageArg,
|
DeleteImageArg,
|
||||||
DeleteImageThunkConfig
|
DeleteImageThunkConfig
|
||||||
>('api/imageDeleted', async (arg, { rejectWithValue }) => {
|
>('thunkApi/imageDeleted', async (arg, { rejectWithValue }) => {
|
||||||
const { image_name } = arg;
|
const { image_name } = arg;
|
||||||
const { del } = $client.get();
|
const { del } = $client.get();
|
||||||
const { data, error, response } = await del('/api/v1/images/{image_name}', {
|
const { data, error, response } = await del('/api/v1/images/{image_name}', {
|
||||||
@ -235,7 +233,7 @@ export const imageUpdated = createAppAsyncThunk<
|
|||||||
UpdateImageResponse,
|
UpdateImageResponse,
|
||||||
UpdateImageArg,
|
UpdateImageArg,
|
||||||
UpdateImageThunkConfig
|
UpdateImageThunkConfig
|
||||||
>('api/imageUpdated', async (arg, { rejectWithValue }) => {
|
>('thunkApi/imageUpdated', async (arg, { rejectWithValue }) => {
|
||||||
const { image_name, image_category, is_intermediate, session_id } = arg;
|
const { image_name, image_category, is_intermediate, session_id } = arg;
|
||||||
const { patch } = $client.get();
|
const { patch } = $client.get();
|
||||||
const { data, error, response } = await patch('/api/v1/images/{image_name}', {
|
const { data, error, response } = await patch('/api/v1/images/{image_name}', {
|
||||||
@ -277,6 +275,7 @@ type ListImagesThunkConfig = {
|
|||||||
error: unknown;
|
error: unknown;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `ImagesService.listImagesWithMetadata()` thunk
|
* `ImagesService.listImagesWithMetadata()` thunk
|
||||||
*/
|
*/
|
||||||
@ -284,46 +283,156 @@ export const receivedPageOfImages = createAppAsyncThunk<
|
|||||||
ListImagesResponse,
|
ListImagesResponse,
|
||||||
ListImagesArg,
|
ListImagesArg,
|
||||||
ListImagesThunkConfig
|
ListImagesThunkConfig
|
||||||
>('api/receivedPageOfImages', async (arg, { getState, rejectWithValue }) => {
|
>(
|
||||||
const { get } = $client.get();
|
'thunkApi/receivedPageOfImages',
|
||||||
|
async (arg, { getState, rejectWithValue }) => {
|
||||||
|
const { get } = $client.get();
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const { categories, selectedBoardId } = state.gallery;
|
const { categories, selectedBoardId } = state.gallery;
|
||||||
|
|
||||||
const images = selectImagesAll(state).filter((i) => {
|
const images = selectImagesAll(state).filter((i) => {
|
||||||
const isInCategory = categories.includes(i.image_category);
|
const isInCategory = categories.includes(i.image_category);
|
||||||
const isInSelectedBoard = selectedBoardId
|
const isInSelectedBoard = selectedBoardId
|
||||||
? i.board_id === selectedBoardId
|
? i.board_id === selectedBoardId
|
||||||
: true;
|
: true;
|
||||||
return isInCategory && isInSelectedBoard;
|
return isInCategory && isInSelectedBoard;
|
||||||
});
|
});
|
||||||
|
|
||||||
let query: ListImagesArg = {};
|
let query: ListImagesArg = {};
|
||||||
|
|
||||||
if (size(arg)) {
|
if (size(arg)) {
|
||||||
query = {
|
query = {
|
||||||
...DEFAULT_IMAGES_LISTED_ARG,
|
...DEFAULT_IMAGES_LISTED_ARG,
|
||||||
offset: images.length,
|
offset: images.length,
|
||||||
...arg,
|
...arg,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
query = {
|
query = {
|
||||||
...DEFAULT_IMAGES_LISTED_ARG,
|
...DEFAULT_IMAGES_LISTED_ARG,
|
||||||
categories,
|
categories,
|
||||||
offset: images.length,
|
offset: images.length,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error, response } = await get('/api/v1/images/', {
|
||||||
|
params: {
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
querySerializer: (q) => queryString.stringify(q, { arrayFormat: 'none' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return rejectWithValue({ arg, error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const { data, error, response } = await get('/api/v1/images/', {
|
export type ImagesLoadedArg = {
|
||||||
params: {
|
board_id: 'all' | 'none' | (string & Record<never, never>);
|
||||||
query,
|
view: 'images' | 'assets';
|
||||||
},
|
offset: number;
|
||||||
querySerializer: (q) => queryString.stringify(q, { arrayFormat: 'none' }),
|
limit?: number;
|
||||||
});
|
};
|
||||||
|
|
||||||
if (error) {
|
type ImagesLoadedThunkConfig = {
|
||||||
return rejectWithValue({ arg, error });
|
rejectValue: {
|
||||||
|
arg: ImagesLoadedArg;
|
||||||
|
error: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCategories = (view: 'images' | 'assets'): ImageCategory[] => {
|
||||||
|
if (view === 'images') {
|
||||||
|
return ['general'];
|
||||||
}
|
}
|
||||||
|
return ['control', 'mask', 'user', 'other'];
|
||||||
|
};
|
||||||
|
|
||||||
return data;
|
const getBoardId = (
|
||||||
});
|
board_id: 'all' | 'none' | (string & Record<never, never>)
|
||||||
|
) => {
|
||||||
|
if (board_id === 'all') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (board_id === 'none') {
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
return board_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `ImagesService.listImagesWithMetadata()` thunk
|
||||||
|
*/
|
||||||
|
export const imagesLoaded = createAppAsyncThunk<
|
||||||
|
OffsetPaginatedResults_ImageDTO_,
|
||||||
|
ImagesLoadedArg,
|
||||||
|
ImagesLoadedThunkConfig
|
||||||
|
>(
|
||||||
|
'thunkApi/imagesLoaded',
|
||||||
|
async (arg, { getState, rejectWithValue, requestId }) => {
|
||||||
|
const { get } = $client.get();
|
||||||
|
|
||||||
|
// TODO: do not make request if request in progress
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
categories: getCategories(arg.view),
|
||||||
|
board_id: getBoardId(arg.board_id),
|
||||||
|
offset: arg.offset,
|
||||||
|
limit: arg.limit ?? IMAGES_PER_PAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data, error, response } = await get('/api/v1/images/', {
|
||||||
|
params: {
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
querySerializer: (q) => queryString.stringify(q, { arrayFormat: 'none' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return rejectWithValue({ arg, error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
type GetImagesByNamesArg = NonNullable<
|
||||||
|
paths['/api/v1/images/']['post']['requestBody']['content']['application/json']
|
||||||
|
>;
|
||||||
|
|
||||||
|
type GetImagesByNamesResponse =
|
||||||
|
paths['/api/v1/images/']['post']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
type GetImagesByNamesThunkConfig = {
|
||||||
|
rejectValue: {
|
||||||
|
arg: GetImagesByNamesArg;
|
||||||
|
error: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `ImagesService.GetImagesByNamesWithMetadata()` thunk
|
||||||
|
*/
|
||||||
|
export const receivedListOfImages = createAppAsyncThunk<
|
||||||
|
GetImagesByNamesResponse,
|
||||||
|
GetImagesByNamesArg,
|
||||||
|
GetImagesByNamesThunkConfig
|
||||||
|
>(
|
||||||
|
'thunkApi/receivedListOfImages',
|
||||||
|
async (arg, { getState, rejectWithValue }) => {
|
||||||
|
const { post } = $client.get();
|
||||||
|
|
||||||
|
const { data, error, response } = await post('/api/v1/images/', {
|
||||||
|
body: arg,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return rejectWithValue({ arg, error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -5,8 +5,13 @@ const subtext = defineStyle((props) => ({
|
|||||||
color: mode('colors.base.500', 'colors.base.400')(props),
|
color: mode('colors.base.500', 'colors.base.400')(props),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const destructive = defineStyle((props) => ({
|
||||||
|
color: mode('colors.error.600', 'colors.error.300')(props),
|
||||||
|
}));
|
||||||
|
|
||||||
export const textTheme = defineStyleConfig({
|
export const textTheme = defineStyleConfig({
|
||||||
variants: {
|
variants: {
|
||||||
subtext,
|
subtext,
|
||||||
|
destructive,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1175,14 +1175,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
|
||||||
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
|
integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0":
|
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.3.0":
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
"@eslint-community/regexpp@^4.4.0":
|
"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.5.0":
|
||||||
version "4.5.1"
|
version "4.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884"
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884"
|
||||||
integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==
|
integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==
|
||||||
@ -1982,7 +1982,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
|
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
|
||||||
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
|
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
|
||||||
|
|
||||||
"@types/json-schema@*", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.9":
|
"@types/json-schema@*", "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6":
|
||||||
version "7.0.12"
|
version "7.0.12"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||||
@ -2086,49 +2086,53 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b"
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b"
|
||||||
integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==
|
integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^5.60.0":
|
"@typescript-eslint/eslint-plugin@^6.0.0":
|
||||||
version "5.60.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.0.tgz#2f4bea6a3718bed2ba52905358d0f45cd3620d31"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.0.0.tgz#19ff4f1cab8d6f8c2c1825150f7a840bc5d9bdc4"
|
||||||
integrity sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==
|
integrity sha512-xuv6ghKGoiq856Bww/yVYnXGsKa588kY3M0XK7uUW/3fJNNULKRfZfSBkMTSpqGG/8ZCXCadfh8G/z/B4aqS/A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/regexpp" "^4.4.0"
|
"@eslint-community/regexpp" "^4.5.0"
|
||||||
"@typescript-eslint/scope-manager" "5.60.0"
|
"@typescript-eslint/scope-manager" "6.0.0"
|
||||||
"@typescript-eslint/type-utils" "5.60.0"
|
"@typescript-eslint/type-utils" "6.0.0"
|
||||||
"@typescript-eslint/utils" "5.60.0"
|
"@typescript-eslint/utils" "6.0.0"
|
||||||
|
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
grapheme-splitter "^1.0.4"
|
grapheme-splitter "^1.0.4"
|
||||||
ignore "^5.2.0"
|
graphemer "^1.4.0"
|
||||||
|
ignore "^5.2.4"
|
||||||
|
natural-compare "^1.4.0"
|
||||||
natural-compare-lite "^1.4.0"
|
natural-compare-lite "^1.4.0"
|
||||||
semver "^7.3.7"
|
semver "^7.5.0"
|
||||||
tsutils "^3.21.0"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^5.60.0":
|
"@typescript-eslint/parser@^6.0.0":
|
||||||
version "5.60.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.60.0.tgz#08f4daf5fc6548784513524f4f2f359cebb4068a"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.0.0.tgz#46b2600fd1f67e62fc00a28093a75f41bf7effc4"
|
||||||
integrity sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==
|
integrity sha512-TNaufYSPrr1U8n+3xN+Yp9g31vQDJqhXzzPSHfQDLcaO4tU+mCfODPxCwf4H530zo7aUBE3QIdxCXamEnG04Tg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/scope-manager" "5.60.0"
|
"@typescript-eslint/scope-manager" "6.0.0"
|
||||||
"@typescript-eslint/types" "5.60.0"
|
"@typescript-eslint/types" "6.0.0"
|
||||||
"@typescript-eslint/typescript-estree" "5.60.0"
|
"@typescript-eslint/typescript-estree" "6.0.0"
|
||||||
|
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
|
||||||
"@typescript-eslint/scope-manager@5.60.0":
|
"@typescript-eslint/scope-manager@6.0.0":
|
||||||
version "5.60.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz#ae511967b4bd84f1d5e179bb2c82857334941c1c"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.0.0.tgz#8ede47a37cb2b7ed82d329000437abd1113b5e11"
|
||||||
integrity sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==
|
integrity sha512-o4q0KHlgCZTqjuaZ25nw5W57NeykZT9LiMEG4do/ovwvOcPnDO1BI5BQdCsUkjxFyrCL0cSzLjvIMfR9uo7cWg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.60.0"
|
"@typescript-eslint/types" "6.0.0"
|
||||||
"@typescript-eslint/visitor-keys" "5.60.0"
|
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||||
|
|
||||||
"@typescript-eslint/type-utils@5.60.0":
|
"@typescript-eslint/type-utils@6.0.0":
|
||||||
version "5.60.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.60.0.tgz#69b09087eb12d7513d5b07747e7d47f5533aa228"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.0.0.tgz#0478d8a94f05e51da2877cc0500f1b3c27ac7e18"
|
||||||
integrity sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g==
|
integrity sha512-ah6LJvLgkoZ/pyJ9GAdFkzeuMZ8goV6BH7eC9FPmojrnX9yNCIsfjB+zYcnex28YO3RFvBkV6rMV6WpIqkPvoQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/typescript-estree" "5.60.0"
|
"@typescript-eslint/typescript-estree" "6.0.0"
|
||||||
"@typescript-eslint/utils" "5.60.0"
|
"@typescript-eslint/utils" "6.0.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
tsutils "^3.21.0"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/types@4.33.0":
|
"@typescript-eslint/types@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
@ -2140,18 +2144,23 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.0.tgz#3179962b28b4790de70e2344465ec97582ce2558"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.0.tgz#3179962b28b4790de70e2344465ec97582ce2558"
|
||||||
integrity sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==
|
integrity sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@5.60.0", "@typescript-eslint/typescript-estree@^5.55.0":
|
"@typescript-eslint/types@6.0.0":
|
||||||
version "5.60.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.0.0.tgz#19795f515f8decbec749c448b0b5fc76d82445a1"
|
||||||
integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==
|
integrity sha512-Zk9KDggyZM6tj0AJWYYKgF0yQyrcnievdhG0g5FqyU3Y2DRxJn4yWY21sJC0QKBckbsdKKjYDV2yVrrEvuTgxg==
|
||||||
|
|
||||||
|
"@typescript-eslint/typescript-estree@6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.0.0.tgz#1e09aab7320e404fb9f83027ea568ac24e372f81"
|
||||||
|
integrity sha512-2zq4O7P6YCQADfmJ5OTDQTP3ktajnXIRrYAtHM9ofto/CJZV3QfJ89GEaM2BNGeSr1KgmBuLhEkz5FBkS2RQhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@typescript-eslint/types" "5.60.0"
|
"@typescript-eslint/types" "6.0.0"
|
||||||
"@typescript-eslint/visitor-keys" "5.60.0"
|
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
is-glob "^4.0.3"
|
is-glob "^4.0.3"
|
||||||
semver "^7.3.7"
|
semver "^7.5.0"
|
||||||
tsutils "^3.21.0"
|
ts-api-utils "^1.0.1"
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree@^4.33.0":
|
"@typescript-eslint/typescript-estree@^4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
@ -2166,19 +2175,32 @@
|
|||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
"@typescript-eslint/utils@5.60.0":
|
"@typescript-eslint/typescript-estree@^5.55.0":
|
||||||
version "5.60.0"
|
version "5.60.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.0.tgz#4667c5aece82f9d4f24a667602f0f300864b554c"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600"
|
||||||
integrity sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==
|
integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint-community/eslint-utils" "^4.2.0"
|
|
||||||
"@types/json-schema" "^7.0.9"
|
|
||||||
"@types/semver" "^7.3.12"
|
|
||||||
"@typescript-eslint/scope-manager" "5.60.0"
|
|
||||||
"@typescript-eslint/types" "5.60.0"
|
"@typescript-eslint/types" "5.60.0"
|
||||||
"@typescript-eslint/typescript-estree" "5.60.0"
|
"@typescript-eslint/visitor-keys" "5.60.0"
|
||||||
eslint-scope "^5.1.1"
|
debug "^4.3.4"
|
||||||
|
globby "^11.1.0"
|
||||||
|
is-glob "^4.0.3"
|
||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/utils@6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.0.0.tgz#27a16d0d8f2719274a39417b9782f7daa3802db0"
|
||||||
|
integrity sha512-SOr6l4NB6HE4H/ktz0JVVWNXqCJTOo/mHnvIte1ZhBQ0Cvd04x5uKZa3zT6tiodL06zf5xxdK8COiDvPnQ27JQ==
|
||||||
|
dependencies:
|
||||||
|
"@eslint-community/eslint-utils" "^4.3.0"
|
||||||
|
"@types/json-schema" "^7.0.11"
|
||||||
|
"@types/semver" "^7.3.12"
|
||||||
|
"@typescript-eslint/scope-manager" "6.0.0"
|
||||||
|
"@typescript-eslint/types" "6.0.0"
|
||||||
|
"@typescript-eslint/typescript-estree" "6.0.0"
|
||||||
|
eslint-scope "^5.1.1"
|
||||||
|
semver "^7.5.0"
|
||||||
|
|
||||||
"@typescript-eslint/visitor-keys@4.33.0":
|
"@typescript-eslint/visitor-keys@4.33.0":
|
||||||
version "4.33.0"
|
version "4.33.0"
|
||||||
@ -2196,6 +2218,14 @@
|
|||||||
"@typescript-eslint/types" "5.60.0"
|
"@typescript-eslint/types" "5.60.0"
|
||||||
eslint-visitor-keys "^3.3.0"
|
eslint-visitor-keys "^3.3.0"
|
||||||
|
|
||||||
|
"@typescript-eslint/visitor-keys@6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.0.0.tgz#0b49026049fbd096d2c00c5e784866bc69532a31"
|
||||||
|
integrity sha512-cvJ63l8c0yXdeT5POHpL0Q1cZoRcmRKFCtSjNGJxPkcP571EfZMcNbzWAc7oK3D1dRzm/V5EwtkANTZxqvuuUA==
|
||||||
|
dependencies:
|
||||||
|
"@typescript-eslint/types" "6.0.0"
|
||||||
|
eslint-visitor-keys "^3.4.1"
|
||||||
|
|
||||||
"@vitejs/plugin-react-swc@^3.3.2":
|
"@vitejs/plugin-react-swc@^3.3.2":
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz#34a82c1728066f48a86dfecb2f15df60f89207fb"
|
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz#34a82c1728066f48a86dfecb2f15df60f89207fb"
|
||||||
@ -3426,7 +3456,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
|
||||||
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
||||||
|
|
||||||
eslint@^8.43.0:
|
eslint@^8.44.0:
|
||||||
version "8.44.0"
|
version "8.44.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500"
|
||||||
integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==
|
integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==
|
||||||
@ -4069,7 +4099,7 @@ ieee754@^1.1.13:
|
|||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||||
|
|
||||||
ignore@^5.2.0:
|
ignore@^5.2.0, ignore@^5.2.4:
|
||||||
version "5.2.4"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||||
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
||||||
@ -5795,6 +5825,13 @@ semver@^7.3.5, semver@^7.3.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
semver@^7.5.0:
|
||||||
|
version "7.5.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
|
||||||
|
integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
semver@~7.3.0:
|
semver@~7.3.0:
|
||||||
version "7.3.8"
|
version "7.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
||||||
@ -6235,6 +6272,11 @@ tree-kill@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||||
|
|
||||||
|
ts-api-utils@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d"
|
||||||
|
integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==
|
||||||
|
|
||||||
ts-easing@^0.2.0:
|
ts-easing@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
|
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
|
||||||
@ -6338,6 +6380,11 @@ typed-array-length@^1.0.4:
|
|||||||
for-each "^0.3.3"
|
for-each "^0.3.3"
|
||||||
is-typed-array "^1.1.9"
|
is-typed-array "^1.1.9"
|
||||||
|
|
||||||
|
typescript-eslint@^0.0.1-alpha.0:
|
||||||
|
version "0.0.1-alpha.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-0.0.1-alpha.0.tgz#285d68a4e96588295cd436278801bcb6a6b916c1"
|
||||||
|
integrity sha512-1hNKM37dAWML/2ltRXupOq2uqcdRQyDFphl+341NTPXFLLLiDhErXx8VtaSLh3xP7SyHZdcCgpt9boYYVb3fQg==
|
||||||
|
|
||||||
typescript@^3.9.10, typescript@^3.9.7:
|
typescript@^3.9.10, typescript@^3.9.7:
|
||||||
version "3.9.10"
|
version "3.9.10"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
|
||||||
@ -6348,6 +6395,11 @@ typescript@^4.0.0, typescript@^4.9.5:
|
|||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||||
|
|
||||||
|
typescript@^5.1.6:
|
||||||
|
version "5.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274"
|
||||||
|
integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==
|
||||||
|
|
||||||
typescript@~5.0.4:
|
typescript@~5.0.4:
|
||||||
version "5.0.4"
|
version "5.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
|
||||||
|
Reference in New Issue
Block a user