wip attempt to rewrite to use no adapter

This commit is contained in:
psychedelicious 2023-07-11 16:13:22 +10:00
parent 2990fa23fe
commit 704cfd8ff5
31 changed files with 870 additions and 562 deletions

View File

@ -1,11 +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.models.image import (AddManyImagesToBoardResult, from invokeai.app.models.image import (AddManyImagesToBoardResult,
GetAllBoardImagesForBoardResult, GetAllBoardImagesForBoardResult,
RemoveManyImagesFromBoardResult) RemoveManyImagesFromBoardResult)
from invokeai.app.services.image_record_storage import OffsetPaginatedResults
from invokeai.app.services.models.image_record import ImageDTO
from ..dependencies import ApiDependencies from ..dependencies import ApiDependencies
@ -13,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"},
@ -21,7 +19,7 @@ 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"""

View File

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

View File

@ -113,7 +113,7 @@ class ImageServiceABC(ABC):
pass pass
@abstractmethod @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.""" """Deletes all images on a board."""
pass pass
@ -386,7 +386,7 @@ class ImageService(ImageServiceABC):
deleted_images.append(image_name) deleted_images.append(image_name)
return DeleteManyImagesResult(deleted_images=deleted_images) 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: try:
board_images = ( board_images = (
self._services.board_image_records.get_all_board_images_for_board( 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: 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

View File

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

View File

@ -39,10 +39,7 @@ 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';
@ -125,10 +122,6 @@ addImageToDeleteSelectedListener();
addImageDTOReceivedFulfilledListener(); addImageDTOReceivedFulfilledListener();
addImageDTOReceivedRejectedListener(); addImageDTOReceivedRejectedListener();
// Image URLs
addImageUrlsReceivedFulfilledListener();
addImageUrlsReceivedRejectedListener();
// User Invoked // User Invoked
addUserInvokedCanvasListener(); addUserInvokedCanvasListener();
addUserInvokedNodesListener(); addUserInvokedNodesListener();
@ -184,6 +177,7 @@ addSessionCanceledRejectedListener();
// Fetching images // Fetching images
addReceivedPageOfImagesListener(); addReceivedPageOfImagesListener();
addImagesLoadedListener();
// ControlNet // ControlNet
addControlNetImageProcessedListener(); addControlNetImageProcessedListener();

View File

@ -1,8 +1,4 @@
import { log } from 'app/logging/useLogger'; import { log } from 'app/logging/useLogger';
import {
imageUpdatedMany,
imageUpdatedOne,
} from 'features/gallery/store/gallerySlice';
import { boardImagesApi } from 'services/api/endpoints/boardImages'; import { boardImagesApi } from 'services/api/endpoints/boardImages';
import { startAppListening } from '..'; import { startAppListening } from '..';
@ -19,13 +15,6 @@ export const addBoardApiListeners = () => {
{ data: { board_id, image_name } }, { data: { board_id, image_name } },
'Image added to board' '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; const { image_name } = action.meta.arg.originalArgs;
moduleLog.debug({ data: { image_name } }, 'Image removed from board'); 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 } }, { data: { board_id, image_names } },
'Images added to board' '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; const { image_names } = action.meta.arg.originalArgs;
moduleLog.debug({ data: { image_names } }, 'Images removed from board'); 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));
}, },
}); });

View File

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

View File

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

View File

@ -1,10 +1,8 @@
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 { requestedBoardImagesDeletion } from 'features/gallery/store/actions'; import { requestedBoardImagesDeletion as requestedBoardAndImagesDeletion } from 'features/gallery/store/actions';
import { import {
imageSelected, imageSelected,
imagesRemoved,
selectImagesAll,
selectImagesById, selectImagesById,
} from 'features/gallery/store/gallerySlice'; } from 'features/gallery/store/gallerySlice';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
@ -15,7 +13,7 @@ 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);

View File

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

View File

@ -1,5 +1,5 @@
import { log } from 'app/logging/useLogger'; 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 { imageDTOReceived, imageUpdated } from 'services/api/thunks/image';
import { startAppListening } from '..'; import { startAppListening } from '..';
@ -33,7 +33,7 @@ export const addImageDTOReceivedFulfilledListener = () => {
} }
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);
}, },
}); });
}; };

View File

@ -2,10 +2,7 @@ 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 { selectFilteredImages } from 'features/gallery/store/gallerySelectors'; import { selectFilteredImages } from 'features/gallery/store/gallerySelectors';
import { import { imageSelected } from 'features/gallery/store/gallerySlice';
imageRemoved,
imageSelected,
} 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) {

View File

@ -2,10 +2,10 @@ import { log } from 'app/logging/useLogger';
import { imageAddedToBatch } from 'features/batch/store/batchSlice'; 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 { imageUpserted } from 'features/gallery/store/gallerySlice';
import { fieldValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice'; import { initialImageChanged } from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { imagesApi } from 'services/api/endpoints/images';
import { imageUploaded } from 'services/api/thunks/image'; import { imageUploaded } from 'services/api/thunks/image';
import { startAppListening } from '..'; import { startAppListening } from '..';
@ -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;
@ -73,7 +74,7 @@ export const addImageUploadedFulfilledListener = () => {
} }
if (postUploadAction?.type === 'ADD_TO_BATCH') { if (postUploadAction?.type === 'ADD_TO_BATCH') {
dispatch(imageAddedToBatch(image)); dispatch(imageAddedToBatch(image.image_name));
return; return;
} }
}, },

View File

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

View File

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

View File

@ -16,6 +16,8 @@ export const addReceivedPageOfImagesListener = () => {
`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)

View File

@ -2,6 +2,7 @@ import { log } from 'app/logging/useLogger';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice'; import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { progressImageSet } from 'features/system/store/systemSlice'; import { progressImageSet } from 'features/system/store/systemSlice';
import { boardImagesApi } from 'services/api/endpoints/boardImages'; import { boardImagesApi } from 'services/api/endpoints/boardImages';
import { imagesApi } from 'services/api/endpoints/images';
import { isImageOutput } from 'services/api/guards'; import { isImageOutput } from 'services/api/guards';
import { imageDTOReceived } from 'services/api/thunks/image'; import { imageDTOReceived } from 'services/api/thunks/image';
import { sessionCanceled } from 'services/api/thunks/session'; import { sessionCanceled } from 'services/api/thunks/session';
@ -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(
imageDTOReceived({ imageDTOReceived({
image_name, image_name,
}) })
); );
const [{ payload: imageDTO }] = await take( 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 // Handle canvas image
@ -59,6 +62,15 @@ 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.addBoardImage.initiate({ boardImagesApi.endpoints.addBoardImage.initiate({
@ -66,6 +78,17 @@ export const addInvocationCompleteEventListener = () => {
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));

View File

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

View File

@ -104,10 +104,10 @@ export const store = configureStore({
// manually type state, cannot type the arg // manually type state, cannot type the arg
// const typedState = state as ReturnType<typeof rootReducer>; // const typedState = state as ReturnType<typeof rootReducer>;
if (action.type.startsWith('api/')) { // if (action.type.startsWith('api/')) {
// don't log api actions, with manual cache updates they are extremely noisy // // don't log api actions, with manual cache updates they are extremely noisy
return false; // return false;
} // }
if (actionsDenylist.includes(action.type)) { if (actionsDenylist.includes(action.type)) {
// don't log other noisy actions // don't log other noisy actions

View File

@ -8,7 +8,7 @@ const AllImagesBoard = ({ isSelected }: { isSelected: boolean }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleAllImagesBoardClick = () => { const handleAllImagesBoardClick = () => {
dispatch(boardIdSelected()); dispatch(boardIdSelected('all'));
}; };
const droppableData: MoveBoardDropData = { const droppableData: MoveBoardDropData = {

View File

@ -118,7 +118,7 @@ const BoardsList = (props: Props) => {
{!searchMode && ( {!searchMode && (
<> <>
<GridItem sx={{ p: 1.5 }}> <GridItem sx={{ p: 1.5 }}>
<AllImagesBoard isSelected={!selectedBoardId} /> <AllImagesBoard isSelected={selectedBoardId === 'all'} />
</GridItem> </GridItem>
<GridItem sx={{ p: 1.5 }}> <GridItem sx={{ p: 1.5 }}>
<BatchBoard isSelected={selectedBoardId === 'batch'} /> <BatchBoard isSelected={selectedBoardId === 'batch'} />

View File

@ -29,16 +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 BatchGrid from './BatchGrid';
import BoardGrid from './BoardGrid';
import BoardsList from './Boards/BoardsList'; import BoardsList from './Boards/BoardsList';
import ImageGalleryGrid from './ImageGalleryGrid'; import ImageGalleryGrid from './ImageGalleryGrid';
@ -68,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();
@ -107,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]);
@ -228,14 +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">
{selectedBoardId === 'batch' ? ( {selectedBoardId === 'batch' ? <BatchGrid /> : <ImageGalleryGrid />}
<BatchGrid />
) : selectedBoardId ? (
<BoardGrid board_id={selectedBoardId} />
) : (
<ImageGalleryGrid />
)}
</Flex> </Flex>
</VStack> </VStack>
); );

View File

@ -1,7 +1,6 @@
import { Box, Flex, Skeleton, Spinner } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { 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 { memo, useCallback, useEffect, useMemo, useRef, useState } from '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 { 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/gallerySelectors';
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 { ImageDTO } from 'services/api/types';
import ItemContainer from './ItemContainer'; import ItemContainer from './ItemContainer';
import ListContainer from './ListContainer'; 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,
@ -69,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;
@ -123,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}`}
imageName={item.image_name}
/>
)
}
/> />
</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}
> >
@ -180,13 +154,6 @@ const ImageGalleryGrid = () => {
</> </>
); );
} }
return (
<IAINoContentFallback
label={t('gallery.noImagesInGallery')}
icon={FaImage}
/>
);
}; };
export default memo(ImageGalleryGrid); export default memo(ImageGalleryGrid);

View File

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

View File

@ -15,4 +15,6 @@ export const galleryPersistDenylist: (keyof typeof initialGalleryState)[] = [
'galleryView', 'galleryView',
'total', 'total',
'isInitialized', 'isInitialized',
'imageNamesByIdAndView',
'statusByIdAndView',
]; ];

View File

@ -1,15 +1,12 @@
import type { PayloadAction, Update } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { dateComparator } from 'common/util/dateComparator'; 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 { 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';
import { selectFilteredImagesLocal } from './gallerySelectors';
export const galleryImagesAdapter = createEntityAdapter<ImageDTO>({ export const galleryImagesAdapter = createEntityAdapter<ImageDTO>({
selectId: (image) => image.image_name, selectId: (image) => image.image_name,
@ -27,6 +24,40 @@ 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 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 = { type AdditionalGalleryState = {
offset: number; offset: number;
limit: number; limit: number;
@ -34,12 +65,54 @@ type AdditionalGalleryState = {
isLoading: boolean; isLoading: boolean;
isFetching: boolean; isFetching: boolean;
categories: ImageCategory[]; categories: ImageCategory[];
selectedBoardId?: 'batch' | 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 =
@ -55,60 +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>) => {
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>) => { imageRemoved: (state, action: PayloadAction<string>) => {
galleryImagesAdapter.removeOne(state, action.payload); galleryImagesAdapter.removeOne(state, action.payload);
}, },
imagesRemoved: (state, action: PayloadAction<string[]>) => { imagesRemoved: (state, action: PayloadAction<string[]>) => {
galleryImagesAdapter.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));
} }
@ -121,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
@ -136,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';
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) { 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) => {
galleryImagesAdapter.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)
);
});
} }
); );
}, },
@ -203,12 +413,7 @@ export const {
} = galleryImagesAdapter.getSelectors<RootState>((state) => state.gallery); } = galleryImagesAdapter.getSelectors<RootState>((state) => state.gallery);
export const { export const {
imageUpserted,
imageUpdatedOne,
imageUpdatedMany,
imageRemoved,
imagesRemoved, imagesRemoved,
imageCategoriesChanged,
imageRangeEndSelected, imageRangeEndSelected,
imageSelectionToggled, imageSelectionToggled,
imageSelected, imageSelected,
@ -220,3 +425,13 @@ export const {
} = gallerySlice.actions; } = gallerySlice.actions;
export default gallerySlice.reducer; 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';

View File

@ -4,7 +4,7 @@ import { components, paths } from '../schema';
import { imagesApi } from './images'; import { imagesApi } from './images';
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 = type AddManyImagesToBoardArg =
paths['/api/v1/board_images/{board_id}/images']['patch']['requestBody']['content']['application/json']; 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 * Board Images Mutations
*/ */
addBoardImage: 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 },

View File

@ -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']
@ -86,7 +86,10 @@ 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<
components['schemas']['DeleteManyImagesResult'],
string
>({
query: (board_id) => ({ query: (board_id) => ({
url: `boards/${board_id}`, url: `boards/${board_id}`,
method: 'DELETE', method: 'DELETE',

View File

@ -200,24 +200,24 @@ export type paths = {
*/ */
patch: operations["update_board"]; 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}": { "/api/v1/board_images/{board_id}": {
/** /**
* Get All Board Images For Board * Get All Board Images For Board
* @description Gets all image names for a board * @description Gets all image names for a board
*/ */
get: operations["get_all_board_images_for_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": { "/api/v1/board_images/{board_id}/images": {
/** /**
@ -346,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: {
/** /**
@ -4478,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;
@ -5418,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 */
@ -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 * 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: {
@ -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 * Create Multiple Board Images
* @description Add many images to a board * @description Add many images to a board

View File

@ -4,6 +4,7 @@ import { size } from 'lodash-es';
import queryString from 'query-string'; import queryString from 'query-string';
import { $client } from 'services/api/client'; import { $client } from 'services/api/client';
import { paths } from 'services/api/schema'; 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'];
@ -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< type GetImagesByNamesArg = NonNullable<
paths['/api/v1/images/']['post']['requestBody']['content']['application/json'] paths['/api/v1/images/']['post']['requestBody']['content']['application/json']
>; >;

View File

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