mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
wip attempt to rewrite to use no adapter
This commit is contained in:
parent
2990fa23fe
commit
704cfd8ff5
@ -1,11 +1,9 @@
|
||||
from fastapi import Body, HTTPException, Path, Query
|
||||
from fastapi import Body, HTTPException, Path
|
||||
from fastapi.routing import APIRouter
|
||||
|
||||
from invokeai.app.models.image import (AddManyImagesToBoardResult,
|
||||
GetAllBoardImagesForBoardResult,
|
||||
RemoveManyImagesFromBoardResult)
|
||||
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
|
||||
from invokeai.app.services.models.image_record import ImageDTO
|
||||
|
||||
from ..dependencies import ApiDependencies
|
||||
|
||||
@ -13,7 +11,7 @@ board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"])
|
||||
|
||||
|
||||
@board_images_router.post(
|
||||
"/",
|
||||
"/{board_id}",
|
||||
operation_id="create_board_image",
|
||||
responses={
|
||||
201: {"description": "The image was added to a board successfully"},
|
||||
@ -21,7 +19,7 @@ board_images_router = APIRouter(prefix="/v1/board_images", tags=["boards"])
|
||||
status_code=201,
|
||||
)
|
||||
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"),
|
||||
):
|
||||
"""Creates a board_image"""
|
||||
|
@ -1,11 +1,13 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from fastapi import Body, HTTPException, Path, Query
|
||||
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.image_record_storage import OffsetPaginatedResults
|
||||
from invokeai.app.services.models.board_record import BoardDTO
|
||||
|
||||
|
||||
from ..dependencies import ApiDependencies
|
||||
|
||||
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")
|
||||
|
||||
|
||||
@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(
|
||||
board_id: str = Path(description="The id of board to delete"),
|
||||
include_images: Optional[bool] = Query(
|
||||
description="Permanently delete all images on the board", default=False
|
||||
),
|
||||
) -> None:
|
||||
) -> DeleteManyImagesResult:
|
||||
"""Deletes a board"""
|
||||
try:
|
||||
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
|
||||
)
|
||||
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
||||
else:
|
||||
ApiDependencies.invoker.services.boards.delete(board_id=board_id)
|
||||
result = DeleteManyImagesResult(deleted_images=[])
|
||||
return result
|
||||
except Exception as e:
|
||||
# TODO: Does this need any exception handling at all?
|
||||
pass
|
||||
raise HTTPException(status_code=500, detail="Failed to delete images on board")
|
||||
|
||||
|
||||
@boards_router.get(
|
||||
|
@ -113,7 +113,7 @@ class ImageServiceABC(ABC):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_images_on_board(self, board_id: str):
|
||||
def delete_images_on_board(self, board_id: str) -> DeleteManyImagesResult:
|
||||
"""Deletes all images on a board."""
|
||||
pass
|
||||
|
||||
@ -386,7 +386,7 @@ class ImageService(ImageServiceABC):
|
||||
deleted_images.append(image_name)
|
||||
return DeleteManyImagesResult(deleted_images=deleted_images)
|
||||
|
||||
def delete_images_on_board(self, board_id: str):
|
||||
def delete_images_on_board(self, board_id: str) -> DeleteManyImagesResult:
|
||||
try:
|
||||
board_images = (
|
||||
self._services.board_image_records.get_all_board_images_for_board(
|
||||
@ -397,6 +397,7 @@ class ImageService(ImageServiceABC):
|
||||
for image_name in image_name_list:
|
||||
self._services.image_files.delete(image_name)
|
||||
self._services.image_records.delete_many(image_name_list)
|
||||
return DeleteManyImagesResult(deleted_images=board_images.image_names)
|
||||
except ImageRecordDeleteException:
|
||||
self._services.logger.error(f"Failed to delete image records")
|
||||
raise
|
||||
|
@ -128,13 +128,13 @@
|
||||
"@types/react-redux": "^7.1.25",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.0",
|
||||
"@typescript-eslint/parser": "^5.60.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"axios": "^1.4.0",
|
||||
"babel-plugin-transform-imports": "^2.0.0",
|
||||
"concurrently": "^8.2.0",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
@ -151,6 +151,8 @@
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"terser": "^5.18.1",
|
||||
"ts-toolbelt": "^9.6.0",
|
||||
"typescript": "^5.1.6",
|
||||
"typescript-eslint": "^0.0.1-alpha.0",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-css-injected-by-js": "^3.1.1",
|
||||
"vite-plugin-dts": "^2.3.0",
|
||||
|
@ -39,10 +39,7 @@ import {
|
||||
addImageUploadedFulfilledListener,
|
||||
addImageUploadedRejectedListener,
|
||||
} from './listeners/imageUploaded';
|
||||
import {
|
||||
addImageUrlsReceivedFulfilledListener,
|
||||
addImageUrlsReceivedRejectedListener,
|
||||
} from './listeners/imageUrlsReceived';
|
||||
import { addImagesLoadedListener } from './listeners/imagesLoaded';
|
||||
import { addInitialImageSelectedListener } from './listeners/initialImageSelected';
|
||||
import { addModelSelectedListener } from './listeners/modelSelected';
|
||||
import { addReceivedOpenAPISchemaListener } from './listeners/receivedOpenAPISchema';
|
||||
@ -125,10 +122,6 @@ addImageToDeleteSelectedListener();
|
||||
addImageDTOReceivedFulfilledListener();
|
||||
addImageDTOReceivedRejectedListener();
|
||||
|
||||
// Image URLs
|
||||
addImageUrlsReceivedFulfilledListener();
|
||||
addImageUrlsReceivedRejectedListener();
|
||||
|
||||
// User Invoked
|
||||
addUserInvokedCanvasListener();
|
||||
addUserInvokedNodesListener();
|
||||
@ -184,6 +177,7 @@ addSessionCanceledRejectedListener();
|
||||
|
||||
// Fetching images
|
||||
addReceivedPageOfImagesListener();
|
||||
addImagesLoadedListener();
|
||||
|
||||
// ControlNet
|
||||
addControlNetImageProcessedListener();
|
||||
|
@ -1,8 +1,4 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import {
|
||||
imageUpdatedMany,
|
||||
imageUpdatedOne,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { boardImagesApi } from 'services/api/endpoints/boardImages';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
@ -19,13 +15,6 @@ export const addBoardApiListeners = () => {
|
||||
{ data: { board_id, image_name } },
|
||||
'Image added to board'
|
||||
);
|
||||
|
||||
dispatch(
|
||||
imageUpdatedOne({
|
||||
id: image_name,
|
||||
changes: { board_id },
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@ -49,13 +38,6 @@ export const addBoardApiListeners = () => {
|
||||
const { image_name } = action.meta.arg.originalArgs;
|
||||
|
||||
moduleLog.debug({ data: { image_name } }, 'Image removed from board');
|
||||
|
||||
dispatch(
|
||||
imageUpdatedOne({
|
||||
id: image_name,
|
||||
changes: { board_id: undefined },
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@ -82,13 +64,6 @@ export const addBoardApiListeners = () => {
|
||||
{ data: { board_id, image_names } },
|
||||
'Images added to board'
|
||||
);
|
||||
|
||||
const updates = image_names.map((image_name) => ({
|
||||
id: image_name,
|
||||
changes: { board_id },
|
||||
}));
|
||||
|
||||
dispatch(imageUpdatedMany(updates));
|
||||
},
|
||||
});
|
||||
|
||||
@ -112,13 +87,6 @@ export const addBoardApiListeners = () => {
|
||||
const { image_names } = action.meta.arg.originalArgs;
|
||||
|
||||
moduleLog.debug({ data: { image_names } }, 'Images removed from board');
|
||||
|
||||
const updates = image_names.map((image_name) => ({
|
||||
id: image_name,
|
||||
changes: { board_id: undefined },
|
||||
}));
|
||||
|
||||
dispatch(imageUpdatedMany(updates));
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,9 +1,4 @@
|
||||
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 '..';
|
||||
|
||||
export const appStarted = createAction('app/appStarted');
|
||||
@ -15,29 +10,27 @@ export const addAppStartedListener = () => {
|
||||
action,
|
||||
{ getState, dispatch, unsubscribe, cancelActiveListeners }
|
||||
) => {
|
||||
cancelActiveListeners();
|
||||
unsubscribe();
|
||||
// fill up the gallery tab with images
|
||||
await dispatch(
|
||||
receivedPageOfImages({
|
||||
categories: ['general'],
|
||||
is_intermediate: false,
|
||||
offset: 0,
|
||||
limit: INITIAL_IMAGE_LIMIT,
|
||||
})
|
||||
);
|
||||
|
||||
// fill up the assets tab with images
|
||||
await dispatch(
|
||||
receivedPageOfImages({
|
||||
categories: ['control', 'mask', 'user', 'other'],
|
||||
is_intermediate: false,
|
||||
offset: 0,
|
||||
limit: INITIAL_IMAGE_LIMIT,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(isLoadingChanged(false));
|
||||
// cancelActiveListeners();
|
||||
// unsubscribe();
|
||||
// // fill up the gallery tab with images
|
||||
// await dispatch(
|
||||
// receivedPageOfImages({
|
||||
// categories: ['general'],
|
||||
// is_intermediate: false,
|
||||
// offset: 0,
|
||||
// // limit: INITIAL_IMAGE_LIMIT,
|
||||
// })
|
||||
// );
|
||||
// // fill up the assets tab with images
|
||||
// await dispatch(
|
||||
// receivedPageOfImages({
|
||||
// categories: ['control', 'mask', 'user', 'other'],
|
||||
// is_intermediate: false,
|
||||
// offset: 0,
|
||||
// // limit: INITIAL_IMAGE_LIMIT,
|
||||
// })
|
||||
// );
|
||||
// dispatch(isLoadingChanged(false));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,15 +1,6 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||
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' });
|
||||
|
||||
@ -17,49 +8,40 @@ export const addBoardIdSelectedListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: boardIdSelected,
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
const board_id = action.payload;
|
||||
|
||||
// we need to check if we need to fetch more images
|
||||
|
||||
const state = getState();
|
||||
const allImages = selectImagesAll(state);
|
||||
|
||||
if (!board_id) {
|
||||
// a board was unselected
|
||||
dispatch(imageSelected(allImages[0]?.image_name));
|
||||
return;
|
||||
}
|
||||
|
||||
const { categories } = state.gallery;
|
||||
|
||||
const filteredImages = allImages.filter((i) => {
|
||||
const isInCategory = categories.includes(i.image_category);
|
||||
const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
||||
return isInCategory && isInSelectedBoard;
|
||||
});
|
||||
|
||||
// get the board from the cache
|
||||
const { data: boards } =
|
||||
boardsApi.endpoints.listAllBoards.select()(state);
|
||||
const board = boards?.find((b) => b.board_id === board_id);
|
||||
|
||||
if (!board) {
|
||||
// can't find the board in cache...
|
||||
dispatch(imageSelected(allImages[0]?.image_name));
|
||||
return;
|
||||
}
|
||||
|
||||
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 })
|
||||
);
|
||||
}
|
||||
// const board_id = action.payload;
|
||||
// // we need to check if we need to fetch more images
|
||||
// const state = getState();
|
||||
// const allImages = selectImagesAll(state);
|
||||
// if (!board_id) {
|
||||
// // a board was unselected
|
||||
// dispatch(imageSelected(allImages[0]?.image_name));
|
||||
// return;
|
||||
// }
|
||||
// const { categories } = state.gallery;
|
||||
// const filteredImages = allImages.filter((i) => {
|
||||
// const isInCategory = categories.includes(i.image_category);
|
||||
// const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
||||
// return isInCategory && isInSelectedBoard;
|
||||
// });
|
||||
// // get the board from the cache
|
||||
// const { data: boards } =
|
||||
// boardsApi.endpoints.listAllBoards.select()(state);
|
||||
// const board = boards?.find((b) => b.board_id === board_id);
|
||||
// if (!board) {
|
||||
// // can't find the board in cache...
|
||||
// dispatch(imageSelected(allImages[0]?.image_name));
|
||||
// return;
|
||||
// }
|
||||
// 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({
|
||||
actionCreator: boardIdSelected,
|
||||
effect: (action, { getState, dispatch }) => {
|
||||
const board_id = action.payload;
|
||||
|
||||
const state = getState();
|
||||
|
||||
// we need to check if we need to fetch more images
|
||||
|
||||
if (!board_id) {
|
||||
// a board was unselected - we don't need to do anything
|
||||
return;
|
||||
}
|
||||
|
||||
const { categories } = state.gallery;
|
||||
|
||||
const filteredImages = selectImagesAll(state).filter((i) => {
|
||||
const isInCategory = categories.includes(i.image_category);
|
||||
const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
||||
return isInCategory && isInSelectedBoard;
|
||||
});
|
||||
|
||||
// get the board from the cache
|
||||
const { data: boards } =
|
||||
boardsApi.endpoints.listAllBoards.select()(state);
|
||||
const board = boards?.find((b) => b.board_id === board_id);
|
||||
if (!board) {
|
||||
// can't find the board in cache...
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 })
|
||||
);
|
||||
}
|
||||
// const board_id = action.payload;
|
||||
// const state = getState();
|
||||
// // we need to check if we need to fetch more images
|
||||
// if (!board_id) {
|
||||
// // a board was unselected - we don't need to do anything
|
||||
// return;
|
||||
// }
|
||||
// const { categories } = state.gallery;
|
||||
// const filteredImages = selectImagesAll(state).filter((i) => {
|
||||
// const isInCategory = categories.includes(i.image_category);
|
||||
// const isInSelectedBoard = board_id ? i.board_id === board_id : true;
|
||||
// return isInCategory && isInSelectedBoard;
|
||||
// });
|
||||
// // get the board from the cache
|
||||
// const { data: boards } =
|
||||
// boardsApi.endpoints.listAllBoards.select()(state);
|
||||
// const board = boards?.find((b) => b.board_id === board_id);
|
||||
// if (!board) {
|
||||
// // can't find the board in cache...
|
||||
// return;
|
||||
// }
|
||||
// // 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,10 +1,8 @@
|
||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||
import { requestedBoardImagesDeletion } from 'features/gallery/store/actions';
|
||||
import { requestedBoardImagesDeletion as requestedBoardAndImagesDeletion } from 'features/gallery/store/actions';
|
||||
import {
|
||||
imageSelected,
|
||||
imagesRemoved,
|
||||
selectImagesAll,
|
||||
selectImagesById,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
|
||||
@ -15,7 +13,7 @@ import { boardsApi } from '../../../../../services/api/endpoints/boards';
|
||||
|
||||
export const addRequestedBoardImageDeletionListener = () => {
|
||||
startAppListening({
|
||||
actionCreator: requestedBoardImagesDeletion,
|
||||
actionCreator: requestedBoardAndImagesDeletion,
|
||||
effect: async (action, { dispatch, getState, condition }) => {
|
||||
const { board, imagesUsage } = action.payload;
|
||||
|
||||
@ -51,20 +49,12 @@ export const addRequestedBoardImageDeletionListener = () => {
|
||||
dispatch(nodeEditorReset());
|
||||
}
|
||||
|
||||
// Preemptively remove from gallery
|
||||
const images = selectImagesAll(state).reduce((acc: string[], img) => {
|
||||
if (img.board_id === board_id) {
|
||||
acc.push(img.image_name);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
dispatch(imagesRemoved(images));
|
||||
|
||||
// Delete from server
|
||||
dispatch(boardsApi.endpoints.deleteBoardAndImages.initiate(board_id));
|
||||
const result =
|
||||
boardsApi.endpoints.deleteBoardAndImages.select(board_id)(state);
|
||||
const { isSuccess } = result;
|
||||
|
||||
const { isSuccess, data } = result;
|
||||
|
||||
// Wait for successful deletion, then trigger boards to re-fetch
|
||||
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 { imageUploaded } from 'services/api/thunks/image';
|
||||
import { canvasSavedToGallery } from 'features/canvas/store/actions';
|
||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||
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' });
|
||||
|
||||
@ -49,7 +49,11 @@ export const addCanvasSavedToGalleryListener = () => {
|
||||
uploadedImageAction.meta.requestId === imageUploadedRequest.requestId
|
||||
);
|
||||
|
||||
dispatch(imageUpserted(uploadedImageDTO));
|
||||
imagesApi.util.upsertQueryData(
|
||||
'getImageDTO',
|
||||
uploadedImageDTO.image_name,
|
||||
uploadedImageDTO
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { imageDTOReceived, imageUpdated } from 'services/api/thunks/image';
|
||||
import { startAppListening } from '..';
|
||||
|
||||
@ -33,7 +33,7 @@ export const addImageDTOReceivedFulfilledListener = () => {
|
||||
}
|
||||
|
||||
moduleLog.debug({ data: { image } }, 'Image metadata received');
|
||||
dispatch(imageUpserted(image));
|
||||
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -2,10 +2,7 @@ import { log } from 'app/logging/useLogger';
|
||||
import { resetCanvas } from 'features/canvas/store/canvasSlice';
|
||||
import { controlNetReset } from 'features/controlNet/store/controlNetSlice';
|
||||
import { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
|
||||
import {
|
||||
imageRemoved,
|
||||
imageSelected,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import {
|
||||
imageDeletionConfirmed,
|
||||
isModalOpenChanged,
|
||||
@ -80,9 +77,6 @@ export const addRequestedImageDeletionListener = () => {
|
||||
dispatch(nodeEditorReset());
|
||||
}
|
||||
|
||||
// Preemptively remove from gallery
|
||||
dispatch(imageRemoved(image_name));
|
||||
|
||||
// Delete from server
|
||||
const { requestId } = dispatch(imageDeleted({ image_name }));
|
||||
|
||||
@ -91,7 +85,7 @@ export const addRequestedImageDeletionListener = () => {
|
||||
(action): action is ReturnType<typeof imageDeleted.fulfilled> =>
|
||||
imageDeleted.fulfilled.match(action) &&
|
||||
action.meta.requestId === requestId,
|
||||
30000
|
||||
30_000
|
||||
);
|
||||
|
||||
if (wasImageDeleted) {
|
||||
|
@ -2,10 +2,10 @@ import { log } from 'app/logging/useLogger';
|
||||
import { imageAddedToBatch } from 'features/batch/store/batchSlice';
|
||||
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
|
||||
import { controlNetImageChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
||||
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
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 '..';
|
||||
|
||||
@ -24,7 +24,8 @@ export const addImageUploadedFulfilledListener = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(imageUpserted(image));
|
||||
// update RTK query cache
|
||||
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image);
|
||||
|
||||
const { postUploadAction } = action.meta.arg;
|
||||
|
||||
@ -73,7 +74,7 @@ export const addImageUploadedFulfilledListener = () => {
|
||||
}
|
||||
|
||||
if (postUploadAction?.type === 'ADD_TO_BATCH') {
|
||||
dispatch(imageAddedToBatch(image));
|
||||
dispatch(imageAddedToBatch(image.image_name));
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
@ -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'
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
@ -16,6 +16,8 @@ export const addReceivedPageOfImagesListener = () => {
|
||||
`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) => {
|
||||
dispatch(
|
||||
imagesApi.util.upsertQueryData('getImageDTO', image.image_name, image)
|
||||
|
@ -2,6 +2,7 @@ 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';
|
||||
@ -41,14 +42,16 @@ export const addInvocationCompleteEventListener = () => {
|
||||
const { image_name } = result.image;
|
||||
|
||||
// Get its metadata
|
||||
dispatch(
|
||||
const { requestId } = dispatch(
|
||||
imageDTOReceived({
|
||||
image_name,
|
||||
})
|
||||
);
|
||||
|
||||
const [{ payload: imageDTO }] = await take(
|
||||
imageDTOReceived.fulfilled.match
|
||||
(action): action is ReturnType<typeof imageDTOReceived.fulfilled> =>
|
||||
imageDTOReceived.fulfilled.match(action) &&
|
||||
action.meta.requestId === requestId
|
||||
);
|
||||
|
||||
// Handle canvas image
|
||||
@ -59,6 +62,15 @@ export const addInvocationCompleteEventListener = () => {
|
||||
dispatch(addImageToStagingArea(imageDTO));
|
||||
}
|
||||
|
||||
// Update the RTK Query cache
|
||||
dispatch(
|
||||
imagesApi.util.upsertQueryData(
|
||||
'getImageDTO',
|
||||
imageDTO.image_name,
|
||||
imageDTO
|
||||
)
|
||||
);
|
||||
|
||||
if (boardIdToAddTo && !imageDTO.is_intermediate) {
|
||||
dispatch(
|
||||
boardImagesApi.endpoints.addBoardImage.initiate({
|
||||
@ -66,6 +78,17 @@ export const addInvocationCompleteEventListener = () => {
|
||||
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));
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
||||
import { startAppListening } from '..';
|
||||
import { log } from 'app/logging/useLogger';
|
||||
import { imageUpdated } from 'services/api/thunks/image';
|
||||
import { imageUpserted } from 'features/gallery/store/gallerySlice';
|
||||
import { stagingAreaImageSaved } from 'features/canvas/store/actions';
|
||||
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' });
|
||||
|
||||
@ -43,7 +43,10 @@ export const addStagingAreaImageSavedListener = () => {
|
||||
}
|
||||
|
||||
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' }));
|
||||
}
|
||||
},
|
||||
|
@ -104,10 +104,10 @@ export const store = configureStore({
|
||||
// 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 (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
|
||||
|
@ -8,7 +8,7 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleAllImagesBoardClick = () => {
|
||||
dispatch(boardIdSelected());
|
||||
dispatch(boardIdSelected('all'));
|
||||
};
|
||||
|
||||
const droppableData: MoveBoardDropData = {
|
||||
|
@ -118,7 +118,7 @@ const BoardsList = (props: Props) => {
|
||||
{!searchMode && (
|
||||
<>
|
||||
<GridItem sx={{ p: 1.5 }}>
|
||||
<AllImagesBoard isSelected={!selectedBoardId} />
|
||||
<AllImagesBoard isSelected={selectedBoardId === 'all'} />
|
||||
</GridItem>
|
||||
<GridItem sx={{ p: 1.5 }}>
|
||||
<BatchBoard isSelected={selectedBoardId === 'batch'} />
|
||||
|
@ -29,16 +29,10 @@ import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import {
|
||||
ASSETS_CATEGORIES,
|
||||
IMAGE_CATEGORIES,
|
||||
imageCategoriesChanged,
|
||||
shouldAutoSwitchChanged,
|
||||
} from 'features/gallery/store/gallerySlice';
|
||||
import { shouldAutoSwitchChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import { mode } from 'theme/util/mode';
|
||||
import BatchGrid from './BatchGrid';
|
||||
import BoardGrid from './BoardGrid';
|
||||
import BoardsList from './Boards/BoardsList';
|
||||
import ImageGalleryGrid from './ImageGalleryGrid';
|
||||
|
||||
@ -68,6 +62,7 @@ const ImageGalleryContent = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const resizeObserverRef = useRef<HTMLDivElement>(null);
|
||||
const galleryGridRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
@ -107,12 +102,10 @@ const ImageGalleryContent = () => {
|
||||
};
|
||||
|
||||
const handleClickImagesCategory = useCallback(() => {
|
||||
dispatch(imageCategoriesChanged(IMAGE_CATEGORIES));
|
||||
dispatch(setGalleryView('images'));
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClickAssetsCategory = useCallback(() => {
|
||||
dispatch(imageCategoriesChanged(ASSETS_CATEGORIES));
|
||||
dispatch(setGalleryView('assets'));
|
||||
}, [dispatch]);
|
||||
|
||||
@ -228,14 +221,8 @@ const ImageGalleryContent = () => {
|
||||
<BoardsList isOpen={isBoardListOpen} />
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex direction="column" gap={2} h="full" w="full">
|
||||
{selectedBoardId === 'batch' ? (
|
||||
<BatchGrid />
|
||||
) : selectedBoardId ? (
|
||||
<BoardGrid board_id={selectedBoardId} />
|
||||
) : (
|
||||
<ImageGalleryGrid />
|
||||
)}
|
||||
<Flex ref={galleryGridRef} direction="column" gap={2} h="full" w="full">
|
||||
{selectedBoardId === 'batch' ? <BatchGrid /> : <ImageGalleryGrid />}
|
||||
</Flex>
|
||||
</VStack>
|
||||
);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Box, Flex, Skeleton, Spinner } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { IMAGE_LIMIT } from 'features/gallery/store/gallerySlice';
|
||||
import { useOverlayScrollbars } from 'overlayscrollbars-react';
|
||||
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -13,48 +12,27 @@ 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 { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
|
||||
import { VirtuosoGrid } from 'react-virtuoso';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
import { receivedPageOfImages } from 'services/api/thunks/image';
|
||||
import { ImageDTO } from 'services/api/types';
|
||||
import { useLoadMoreImages } from '../hooks/useLoadMoreImages';
|
||||
import ItemContainer from './ItemContainer';
|
||||
import ListContainer from './ListContainer';
|
||||
|
||||
const selector = createSelector(
|
||||
[stateSelector, selectFilteredImages],
|
||||
(state, filteredImages) => {
|
||||
const {
|
||||
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'));
|
||||
}
|
||||
[stateSelector],
|
||||
(state) => {
|
||||
const { galleryImageMinimumWidth } = state.gallery;
|
||||
|
||||
return {
|
||||
images,
|
||||
allImagesTotal,
|
||||
isLoading,
|
||||
isFetching,
|
||||
categories,
|
||||
selectedBoardId,
|
||||
galleryImageMinimumWidth,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const ImageGalleryGrid = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
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 [initialize, osInstance] = useOverlayScrollbars({
|
||||
defer: true,
|
||||
@ -69,46 +47,27 @@ const ImageGalleryGrid = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { galleryImageMinimumWidth } = useAppSelector(selector);
|
||||
|
||||
const {
|
||||
images,
|
||||
isLoading,
|
||||
isFetching,
|
||||
allImagesTotal,
|
||||
categories,
|
||||
imageNames,
|
||||
galleryView,
|
||||
loadMoreImages,
|
||||
selectedBoardId,
|
||||
} = useAppSelector(selector);
|
||||
|
||||
const { selectedBoard } = useListAllBoardsQuery(undefined, {
|
||||
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]);
|
||||
status,
|
||||
areMoreAvailable,
|
||||
} = useLoadMoreImages();
|
||||
|
||||
const handleLoadMoreImages = useCallback(() => {
|
||||
dispatch(
|
||||
receivedPageOfImages({
|
||||
categories,
|
||||
board_id: selectedBoardId,
|
||||
is_intermediate: false,
|
||||
})
|
||||
);
|
||||
}, [categories, dispatch, selectedBoardId]);
|
||||
loadMoreImages({});
|
||||
}, [loadMoreImages]);
|
||||
|
||||
const handleEndReached = useMemo(() => {
|
||||
if (areMoreAvailable && !isLoading) {
|
||||
if (areMoreAvailable && status !== 'pending') {
|
||||
return handleLoadMoreImages;
|
||||
}
|
||||
return undefined;
|
||||
}, [areMoreAvailable, handleLoadMoreImages, isLoading]);
|
||||
}, [areMoreAvailable, handleLoadMoreImages, status]);
|
||||
|
||||
useEffect(() => {
|
||||
const { current: root } = rootRef;
|
||||
@ -123,53 +82,68 @@ const ImageGalleryGrid = () => {
|
||||
return () => osInstance()?.destroy();
|
||||
}, [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 (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Spinner
|
||||
size="xl"
|
||||
sx={{ color: 'base.300', _dark: { color: 'base.700' } }}
|
||||
<Box ref={emptyGalleryRef} sx={{ w: 'full', h: 'full' }}>
|
||||
<IAINoContentFallback
|
||||
label={t('gallery.noImagesInGallery')}
|
||||
icon={FaImage}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (images.length) {
|
||||
if (status !== 'rejected') {
|
||||
return (
|
||||
<>
|
||||
<Box ref={rootRef} data-overlayscrollbars="" h="100%">
|
||||
<VirtuosoGrid
|
||||
style={{ height: '100%' }}
|
||||
data={images}
|
||||
data={imageNames}
|
||||
endReached={handleEndReached}
|
||||
components={{
|
||||
Item: ItemContainer,
|
||||
List: ListContainer,
|
||||
}}
|
||||
scrollerRef={setScroller}
|
||||
itemContent={(index, item) =>
|
||||
typeof item === 'string' ? (
|
||||
<Skeleton sx={{ w: 'full', h: 'full', aspectRatio: '1/1' }} />
|
||||
) : (
|
||||
<GalleryImage
|
||||
key={`${item.image_name}-${item.thumbnail_url}`}
|
||||
imageName={item.image_name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
itemContent={(index, imageName) => (
|
||||
<GalleryImage key={imageName} imageName={imageName} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
<IAIButton
|
||||
onClick={handleLoadMoreImages}
|
||||
isDisabled={!areMoreAvailable}
|
||||
isLoading={isFetching}
|
||||
isLoading={status === 'pending'}
|
||||
loadingText="Loading"
|
||||
flexShrink={0}
|
||||
>
|
||||
@ -180,13 +154,6 @@ const ImageGalleryGrid = () => {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IAINoContentFallback
|
||||
label={t('gallery.noImagesInGallery')}
|
||||
icon={FaImage}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ImageGalleryGrid);
|
||||
|
@ -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',
|
||||
'total',
|
||||
'isInitialized',
|
||||
'imageNamesByIdAndView',
|
||||
'statusByIdAndView',
|
||||
];
|
||||
|
@ -1,15 +1,12 @@
|
||||
import type { PayloadAction, Update } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { dateComparator } from 'common/util/dateComparator';
|
||||
import { 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 {
|
||||
imageUrlsReceived,
|
||||
receivedPageOfImages,
|
||||
} from 'services/api/thunks/image';
|
||||
import { imageDeleted, imagesLoaded } from 'services/api/thunks/image';
|
||||
import { ImageCategory, ImageDTO } from 'services/api/types';
|
||||
import { selectFilteredImagesLocal } from './gallerySelectors';
|
||||
|
||||
export const galleryImagesAdapter = createEntityAdapter<ImageDTO>({
|
||||
selectId: (image) => image.image_name,
|
||||
@ -27,6 +24,40 @@ export const ASSETS_CATEGORIES: ImageCategory[] = [
|
||||
export const INITIAL_IMAGE_LIMIT = 100;
|
||||
export const IMAGE_LIMIT = 20;
|
||||
|
||||
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;
|
||||
limit: number;
|
||||
@ -34,12 +65,54 @@ type AdditionalGalleryState = {
|
||||
isLoading: boolean;
|
||||
isFetching: boolean;
|
||||
categories: ImageCategory[];
|
||||
selectedBoardId?: 'batch' | string;
|
||||
selection: string[];
|
||||
shouldAutoSwitch: boolean;
|
||||
galleryImageMinimumWidth: number;
|
||||
galleryView: 'images' | 'assets';
|
||||
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 =
|
||||
@ -55,60 +128,45 @@ export const initialGalleryState =
|
||||
galleryImageMinimumWidth: 96,
|
||||
galleryView: 'images',
|
||||
isInitialized: false,
|
||||
selectedBoardId: 'all',
|
||||
boards: initialBoards,
|
||||
});
|
||||
|
||||
export const gallerySlice = createSlice({
|
||||
name: 'gallery',
|
||||
initialState: initialGalleryState,
|
||||
reducers: {
|
||||
imageUpserted: (state, action: PayloadAction<ImageDTO>) => {
|
||||
galleryImagesAdapter.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>>) => {
|
||||
galleryImagesAdapter.updateOne(state, action.payload);
|
||||
},
|
||||
imageUpdatedMany: (state, action: PayloadAction<Update<ImageDTO>[]>) => {
|
||||
galleryImagesAdapter.updateMany(state, action.payload);
|
||||
},
|
||||
imageRemoved: (state, action: PayloadAction<string>) => {
|
||||
galleryImagesAdapter.removeOne(state, action.payload);
|
||||
},
|
||||
imagesRemoved: (state, action: PayloadAction<string[]>) => {
|
||||
galleryImagesAdapter.removeMany(state, action.payload);
|
||||
},
|
||||
imageCategoriesChanged: (state, action: PayloadAction<ImageCategory[]>) => {
|
||||
state.categories = action.payload;
|
||||
},
|
||||
imageRangeEndSelected: (state, action: PayloadAction<string>) => {
|
||||
const rangeEndImageName = action.payload;
|
||||
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(
|
||||
(n) => n.image_name === lastSelectedImage
|
||||
// get the index of the last selected image
|
||||
const lastClickedIndex = imageNames.findIndex(
|
||||
(n) => n === lastSelectedImage
|
||||
);
|
||||
|
||||
const currentClickedIndex = filteredImages.findIndex(
|
||||
(n) => n.image_name === rangeEndImageName
|
||||
// get the index of the just-clicked image
|
||||
const currentClickedIndex = imageNames.findIndex(
|
||||
(n) => n === rangeEndImageName
|
||||
);
|
||||
|
||||
if (lastClickedIndex > -1 && currentClickedIndex > -1) {
|
||||
// We have a valid range!
|
||||
// We have a valid range, selected it!
|
||||
const start = Math.min(lastClickedIndex, currentClickedIndex);
|
||||
const end = Math.max(lastClickedIndex, currentClickedIndex);
|
||||
|
||||
const imagesToSelect = filteredImages
|
||||
.slice(start, end + 1)
|
||||
.map((i) => i.image_name);
|
||||
const imagesToSelect = imageNames.slice(start, end + 1);
|
||||
|
||||
state.selection = uniq(state.selection.concat(imagesToSelect));
|
||||
}
|
||||
@ -121,9 +179,10 @@ export const gallerySlice = createSlice({
|
||||
state.selection = state.selection.filter(
|
||||
(imageName) => imageName !== action.payload
|
||||
);
|
||||
} else {
|
||||
state.selection = uniq(state.selection.concat(action.payload));
|
||||
return;
|
||||
}
|
||||
|
||||
state.selection = uniq(state.selection.concat(action.payload));
|
||||
},
|
||||
imageSelected: (state, action: PayloadAction<string | null>) => {
|
||||
state.selection = action.payload
|
||||
@ -136,59 +195,210 @@ export const gallerySlice = createSlice({
|
||||
setGalleryImageMinimumWidth: (state, action: PayloadAction<number>) => {
|
||||
state.galleryImageMinimumWidth = action.payload;
|
||||
},
|
||||
setGalleryView: (state, action: PayloadAction<'images' | 'assets'>) => {
|
||||
setGalleryView: (state, action: PayloadAction<GalleryView>) => {
|
||||
state.galleryView = action.payload;
|
||||
},
|
||||
boardIdSelected: (state, action: PayloadAction<string | undefined>) => {
|
||||
state.selectedBoardId = action.payload;
|
||||
boardIdSelected: (state, action: PayloadAction<BoardPath>) => {
|
||||
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>) => {
|
||||
state.isLoading = action.payload;
|
||||
},
|
||||
},
|
||||
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;
|
||||
const { board_id, categories, image_origin, is_intermediate } =
|
||||
action.meta.arg;
|
||||
/**
|
||||
* Images loaded into gallery - FULFILLED
|
||||
*/
|
||||
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';
|
||||
|
||||
galleryImagesAdapter.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) {
|
||||
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) {
|
||||
// need to skip updating the total images count if the images recieved were for a specific board
|
||||
// TODO: this doesn't work when on the Asset tab/category...
|
||||
return;
|
||||
if (board_id === board.id) {
|
||||
// add image to the board
|
||||
board.imageNames = uniq(board.imageNames.concat(image_name));
|
||||
} else {
|
||||
// remove image from other boards
|
||||
board.imageNames = board.imageNames.filter((n) => n !== image_name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
state.offset = offset;
|
||||
state.total = total;
|
||||
});
|
||||
builder.addCase(imageUrlsReceived.fulfilled, (state, action) => {
|
||||
const { image_name, image_url, thumbnail_url } = action.payload;
|
||||
|
||||
galleryImagesAdapter.updateOne(state, {
|
||||
id: image_name,
|
||||
changes: { image_url, thumbnail_url },
|
||||
});
|
||||
});
|
||||
);
|
||||
/**
|
||||
* Many images added to board
|
||||
*/
|
||||
builder.addMatcher(
|
||||
boardImagesApi.endpoints.addManyBoardImages.matchFulfilled,
|
||||
(state, action) => {
|
||||
const { board_id, image_names } = action.meta.arg.originalArgs;
|
||||
// update local board stores
|
||||
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(
|
||||
boardsApi.endpoints.deleteBoard.matchFulfilled,
|
||||
(state, action) => {
|
||||
if (action.meta.arg.originalArgs === state.selectedBoardId) {
|
||||
state.selectedBoardId = undefined;
|
||||
const deletedBoardId = action.meta.arg.originalArgs;
|
||||
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)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
@ -203,12 +413,7 @@ export const {
|
||||
} = galleryImagesAdapter.getSelectors<RootState>((state) => state.gallery);
|
||||
|
||||
export const {
|
||||
imageUpserted,
|
||||
imageUpdatedOne,
|
||||
imageUpdatedMany,
|
||||
imageRemoved,
|
||||
imagesRemoved,
|
||||
imageCategoriesChanged,
|
||||
imageRangeEndSelected,
|
||||
imageSelectionToggled,
|
||||
imageSelected,
|
||||
@ -220,3 +425,13 @@ export const {
|
||||
} = gallerySlice.actions;
|
||||
|
||||
export default gallerySlice.reducer;
|
||||
|
||||
const selectUserBoards = (state: typeof initialGalleryState) =>
|
||||
filter(state.boards, (board, path) => !systemBoards.includes(path));
|
||||
|
||||
const selectCurrentBoard = (state: typeof initialGalleryState) =>
|
||||
state.boards[`${state.selectedBoardId}.${state.galleryView}`];
|
||||
|
||||
const isImagesView = (board: BoardPath) => board.split('.')[1] === 'images';
|
||||
|
||||
const isAssetsView = (board: BoardPath) => board.split('.')[1] === 'assets';
|
||||
|
@ -4,7 +4,7 @@ import { components, paths } from '../schema';
|
||||
import { imagesApi } from './images';
|
||||
|
||||
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'];
|
||||
@ -44,11 +44,14 @@ export const boardImagesApi = api.injectEndpoints({
|
||||
* Board Images Mutations
|
||||
*/
|
||||
|
||||
addBoardImage: build.mutation<void, AddImageToBoardArg>({
|
||||
addBoardImage: build.mutation<
|
||||
void,
|
||||
{ board_id: string; image_name: string }
|
||||
>({
|
||||
query: ({ board_id, image_name }) => ({
|
||||
url: `board_images/`,
|
||||
url: `board_images/${board_id}`,
|
||||
method: 'POST',
|
||||
body: { board_id, image_name },
|
||||
body: image_name,
|
||||
}),
|
||||
invalidatesTags: (result, error, arg) => [
|
||||
{ type: 'Board', id: arg.board_id },
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BoardDTO, OffsetPaginatedResults_BoardDTO_ } from 'services/api/types';
|
||||
import { ApiFullTagDescription, LIST_TAG, api } from '..';
|
||||
import { paths } from '../schema';
|
||||
import { components, paths } from '../schema';
|
||||
|
||||
type ListBoardsArg = NonNullable<
|
||||
paths['/api/v1/boards/']['get']['parameters']['query']
|
||||
@ -86,7 +86,10 @@ export const boardsApi = api.injectEndpoints({
|
||||
query: (board_id) => ({ url: `boards/${board_id}`, method: 'DELETE' }),
|
||||
invalidatesTags: (result, error, arg) => [{ type: 'Board', id: arg }],
|
||||
}),
|
||||
deleteBoardAndImages: build.mutation<void, string>({
|
||||
deleteBoardAndImages: build.mutation<
|
||||
components['schemas']['DeleteManyImagesResult'],
|
||||
string
|
||||
>({
|
||||
query: (board_id) => ({
|
||||
url: `boards/${board_id}`,
|
||||
method: 'DELETE',
|
||||
|
111
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
111
invokeai/frontend/web/src/services/api/schema.d.ts
vendored
@ -200,24 +200,24 @@ export type paths = {
|
||||
*/
|
||||
patch: operations["update_board"];
|
||||
};
|
||||
"/api/v1/board_images/": {
|
||||
/**
|
||||
* Create Board Image
|
||||
* @description Creates a board_image
|
||||
*/
|
||||
post: operations["create_board_image"];
|
||||
/**
|
||||
* Remove Board Image
|
||||
* @description Deletes a board_image
|
||||
*/
|
||||
delete: operations["remove_board_image"];
|
||||
};
|
||||
"/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
|
||||
* @description Creates a board_image
|
||||
*/
|
||||
post: operations["create_board_image"];
|
||||
};
|
||||
"/api/v1/board_images/": {
|
||||
/**
|
||||
* Remove Board Image
|
||||
* @description Deletes a board_image
|
||||
*/
|
||||
delete: operations["remove_board_image"];
|
||||
};
|
||||
"/api/v1/board_images/{board_id}/images": {
|
||||
/**
|
||||
@ -346,19 +346,6 @@ export type components = {
|
||||
*/
|
||||
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: {
|
||||
/**
|
||||
@ -4478,18 +4465,18 @@ export type components = {
|
||||
*/
|
||||
image?: components["schemas"]["ImageField"];
|
||||
};
|
||||
/**
|
||||
* StableDiffusion2ModelFormat
|
||||
* @description An enumeration.
|
||||
* @enum {string}
|
||||
*/
|
||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||
/**
|
||||
* StableDiffusion1ModelFormat
|
||||
* @description An enumeration.
|
||||
* @enum {string}
|
||||
*/
|
||||
StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
|
||||
/**
|
||||
* StableDiffusion2ModelFormat
|
||||
* @description An enumeration.
|
||||
* @enum {string}
|
||||
*/
|
||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
@ -5418,7 +5405,7 @@ export type operations = {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["DeleteManyImagesResult"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
@ -5460,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
|
||||
* @description Creates a board_image
|
||||
*/
|
||||
create_board_image: {
|
||||
parameters: {
|
||||
path: {
|
||||
/** @description The id of the board to add to */
|
||||
board_id: string;
|
||||
};
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["Body_create_board_image"];
|
||||
"application/json": string;
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
@ -5510,32 +5529,6 @@ 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 Multiple Board Images
|
||||
* @description Add many images to a board
|
||||
|
@ -4,6 +4,7 @@ import { size } from 'lodash-es';
|
||||
import queryString from 'query-string';
|
||||
import { $client } from 'services/api/client';
|
||||
import { paths } from 'services/api/schema';
|
||||
import { ImageCategory, OffsetPaginatedResults_ImageDTO_ } from '../types';
|
||||
|
||||
type GetImageUrlsArg =
|
||||
paths['/api/v1/images/{image_name}/urls']['get']['parameters']['path'];
|
||||
@ -329,6 +330,75 @@ export const receivedPageOfImages = createAppAsyncThunk<
|
||||
}
|
||||
);
|
||||
|
||||
export type ImagesLoadedArg = {
|
||||
board_id: 'all' | 'none' | (string & Record<never, never>);
|
||||
view: 'images' | 'assets';
|
||||
offset: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
type ImagesLoadedThunkConfig = {
|
||||
rejectValue: {
|
||||
arg: ImagesLoadedArg;
|
||||
error: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
const getCategories = (view: 'images' | 'assets'): ImageCategory[] => {
|
||||
if (view === 'images') {
|
||||
return ['general'];
|
||||
}
|
||||
return ['control', 'mask', 'user', 'other'];
|
||||
};
|
||||
|
||||
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']
|
||||
>;
|
||||
|
@ -1175,14 +1175,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884"
|
||||
integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==
|
||||
@ -1982,7 +1982,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
|
||||
integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
|
||||
@ -2086,49 +2086,53 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b"
|
||||
integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.60.0":
|
||||
version "5.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.0.tgz#2f4bea6a3718bed2ba52905358d0f45cd3620d31"
|
||||
integrity sha512-78B+anHLF1TI8Jn/cD0Q00TBYdMgjdOn980JfAVa9yw5sop8nyTfVOQAv6LWywkOGLclDBtv5z3oxN4w7jxyNg==
|
||||
"@typescript-eslint/eslint-plugin@^6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.0.0.tgz#19ff4f1cab8d6f8c2c1825150f7a840bc5d9bdc4"
|
||||
integrity sha512-xuv6ghKGoiq856Bww/yVYnXGsKa588kY3M0XK7uUW/3fJNNULKRfZfSBkMTSpqGG/8ZCXCadfh8G/z/B4aqS/A==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "5.60.0"
|
||||
"@typescript-eslint/type-utils" "5.60.0"
|
||||
"@typescript-eslint/utils" "5.60.0"
|
||||
"@eslint-community/regexpp" "^4.5.0"
|
||||
"@typescript-eslint/scope-manager" "6.0.0"
|
||||
"@typescript-eslint/type-utils" "6.0.0"
|
||||
"@typescript-eslint/utils" "6.0.0"
|
||||
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||
debug "^4.3.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"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
semver "^7.5.0"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/parser@^5.60.0":
|
||||
version "5.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.60.0.tgz#08f4daf5fc6548784513524f4f2f359cebb4068a"
|
||||
integrity sha512-jBONcBsDJ9UoTWrARkRRCgDz6wUggmH5RpQVlt7BimSwaTkTjwypGzKORXbR4/2Hqjk9hgwlon2rVQAjWNpkyQ==
|
||||
"@typescript-eslint/parser@^6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.0.0.tgz#46b2600fd1f67e62fc00a28093a75f41bf7effc4"
|
||||
integrity sha512-TNaufYSPrr1U8n+3xN+Yp9g31vQDJqhXzzPSHfQDLcaO4tU+mCfODPxCwf4H530zo7aUBE3QIdxCXamEnG04Tg==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "5.60.0"
|
||||
"@typescript-eslint/types" "5.60.0"
|
||||
"@typescript-eslint/typescript-estree" "5.60.0"
|
||||
"@typescript-eslint/scope-manager" "6.0.0"
|
||||
"@typescript-eslint/types" "6.0.0"
|
||||
"@typescript-eslint/typescript-estree" "6.0.0"
|
||||
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@5.60.0":
|
||||
version "5.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz#ae511967b4bd84f1d5e179bb2c82857334941c1c"
|
||||
integrity sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ==
|
||||
"@typescript-eslint/scope-manager@6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.0.0.tgz#8ede47a37cb2b7ed82d329000437abd1113b5e11"
|
||||
integrity sha512-o4q0KHlgCZTqjuaZ25nw5W57NeykZT9LiMEG4do/ovwvOcPnDO1BI5BQdCsUkjxFyrCL0cSzLjvIMfR9uo7cWg==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "5.60.0"
|
||||
"@typescript-eslint/visitor-keys" "5.60.0"
|
||||
"@typescript-eslint/types" "6.0.0"
|
||||
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||
|
||||
"@typescript-eslint/type-utils@5.60.0":
|
||||
version "5.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.60.0.tgz#69b09087eb12d7513d5b07747e7d47f5533aa228"
|
||||
integrity sha512-X7NsRQddORMYRFH7FWo6sA9Y/zbJ8s1x1RIAtnlj6YprbToTiQnM6vxcMu7iYhdunmoC0rUWlca13D5DVHkK2g==
|
||||
"@typescript-eslint/type-utils@6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.0.0.tgz#0478d8a94f05e51da2877cc0500f1b3c27ac7e18"
|
||||
integrity sha512-ah6LJvLgkoZ/pyJ9GAdFkzeuMZ8goV6BH7eC9FPmojrnX9yNCIsfjB+zYcnex28YO3RFvBkV6rMV6WpIqkPvoQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "5.60.0"
|
||||
"@typescript-eslint/utils" "5.60.0"
|
||||
"@typescript-eslint/typescript-estree" "6.0.0"
|
||||
"@typescript-eslint/utils" "6.0.0"
|
||||
debug "^4.3.4"
|
||||
tsutils "^3.21.0"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/types@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"
|
||||
integrity sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA==
|
||||
|
||||
"@typescript-eslint/typescript-estree@5.60.0", "@typescript-eslint/typescript-estree@^5.55.0":
|
||||
version "5.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600"
|
||||
integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==
|
||||
"@typescript-eslint/types@6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.0.0.tgz#19795f515f8decbec749c448b0b5fc76d82445a1"
|
||||
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:
|
||||
"@typescript-eslint/types" "5.60.0"
|
||||
"@typescript-eslint/visitor-keys" "5.60.0"
|
||||
"@typescript-eslint/types" "6.0.0"
|
||||
"@typescript-eslint/visitor-keys" "6.0.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
semver "^7.5.0"
|
||||
ts-api-utils "^1.0.1"
|
||||
|
||||
"@typescript-eslint/typescript-estree@^4.33.0":
|
||||
version "4.33.0"
|
||||
@ -2166,19 +2175,32 @@
|
||||
semver "^7.3.5"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/utils@5.60.0":
|
||||
"@typescript-eslint/typescript-estree@^5.55.0":
|
||||
version "5.60.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.0.tgz#4667c5aece82f9d4f24a667602f0f300864b554c"
|
||||
integrity sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ==
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600"
|
||||
integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ==
|
||||
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/typescript-estree" "5.60.0"
|
||||
eslint-scope "^5.1.1"
|
||||
"@typescript-eslint/visitor-keys" "5.60.0"
|
||||
debug "^4.3.4"
|
||||
globby "^11.1.0"
|
||||
is-glob "^4.0.3"
|
||||
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":
|
||||
version "4.33.0"
|
||||
@ -2196,6 +2218,14 @@
|
||||
"@typescript-eslint/types" "5.60.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":
|
||||
version "3.3.2"
|
||||
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"
|
||||
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
||||
|
||||
eslint@^8.43.0:
|
||||
eslint@^8.44.0:
|
||||
version "8.44.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500"
|
||||
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"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
ignore@^5.2.0:
|
||||
ignore@^5.2.0, ignore@^5.2.4:
|
||||
version "5.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
|
||||
@ -5795,6 +5825,13 @@ semver@^7.3.5, semver@^7.3.7:
|
||||
dependencies:
|
||||
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:
|
||||
version "7.3.8"
|
||||
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"
|
||||
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:
|
||||
version "0.2.0"
|
||||
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"
|
||||
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:
|
||||
version "3.9.10"
|
||||
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"
|
||||
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:
|
||||
version "5.0.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
|
||||
|
Loading…
Reference in New Issue
Block a user