Compare commits

..

5 Commits

Author SHA1 Message Date
psychedelicious
358dbdbf84 chore: bump version to v5.2.0 2024-10-17 22:24:51 +11:00
psychedelicious
5ec2d71be0 feat(ui): make debug logger middleware configurable
While troubleshooting an issue with this middleware, I found the inclusion of the nextState and diff to be very noisy. It's now a function that accepts some options to configure the output, and returns the middleware.
2024-10-17 08:04:51 +11:00
Mary Hipp
8f28903c81 remove extra slash in workflow share link 2024-10-17 08:02:27 +11:00
Mary Hipp
a071f2788a fix(ui): upload tooltip should only show plural if multiple upload is an option 2024-10-16 12:00:11 -04:00
Mary Hipp
d9a257ef8a fix(ui): add error handling to upload button 2024-10-16 09:32:35 -04:00
5 changed files with 68 additions and 14 deletions

View File

@@ -7,12 +7,20 @@ import { diff } from 'jsondiffpatch';
/**
* Super simple logger middleware. Useful for debugging when the redux devtools are awkward.
*/
export const debugLoggerMiddleware: Middleware = (api: MiddlewareAPI) => (next) => (action) => {
const originalState = api.getState();
console.log('REDUX: dispatching', action);
const result = next(action);
const nextState = api.getState();
console.log('REDUX: next state', nextState);
console.log('REDUX: diff', diff(originalState, nextState));
return result;
};
export const getDebugLoggerMiddleware =
(options?: { withDiff?: boolean; withNextState?: boolean }): Middleware =>
(api: MiddlewareAPI) =>
(next) =>
(action) => {
const originalState = api.getState();
console.log('REDUX: dispatching', action);
const result = next(action);
const nextState = api.getState();
if (options?.withNextState) {
console.log('REDUX: next state', nextState);
}
if (options?.withDiff) {
console.log('REDUX: diff', diff(originalState, nextState));
}
return result;
};

View File

@@ -1,8 +1,12 @@
import { logger } from 'app/logging/logger';
import { useAppSelector } from 'app/store/storeHooks';
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
import { selectMaxImageUploadCount } from 'features/system/store/configSlice';
import { toast } from 'features/toast/toast';
import { useCallback } from 'react';
import type { FileRejection } from 'react-dropzone';
import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';
import { useUploadImageMutation } from 'services/api/endpoints/images';
import type { PostUploadAction } from 'services/api/types';
@@ -12,6 +16,8 @@ type UseImageUploadButtonArgs = {
allowMultiple?: boolean;
};
const log = logger('gallery');
/**
* Provides image uploader functionality to any component.
*
@@ -39,22 +45,50 @@ export const useImageUploadButton = ({
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
const [uploadImage] = useUploadImageMutation();
const maxImageUploadCount = useAppSelector(selectMaxImageUploadCount);
const { t } = useTranslation();
const onDropAccepted = useCallback(
(files: File[]) => {
for (const file of files) {
for (const [i, file] of files.entries()) {
uploadImage({
file,
image_category: 'user',
is_intermediate: false,
postUploadAction: postUploadAction ?? { type: 'TOAST' },
board_id: autoAddBoardId === 'none' ? undefined : autoAddBoardId,
isFirstUploadOfBatch: i === 0,
});
}
},
[autoAddBoardId, postUploadAction, uploadImage]
);
const onDropRejected = useCallback(
(fileRejections: FileRejection[]) => {
if (fileRejections.length > 0) {
const errors = fileRejections.map((rejection) => ({
errors: rejection.errors.map(({ message }) => message),
file: rejection.file.path,
}));
log.error({ errors }, 'Invalid upload');
const description =
maxImageUploadCount === undefined
? t('toast.uploadFailedInvalidUploadDesc')
: t('toast.uploadFailedInvalidUploadDesc_withCount', { count: maxImageUploadCount });
toast({
id: 'UPLOAD_FAILED',
title: t('toast.uploadFailed'),
description,
status: 'error',
});
return;
}
},
[maxImageUploadCount, t]
);
const {
getRootProps: getUploadButtonProps,
getInputProps: getUploadInputProps,
@@ -62,6 +96,7 @@ export const useImageUploadButton = ({
} = useDropzone({
accept: { 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg', '.png'] },
onDropAccepted,
onDropRejected,
disabled: isDisabled,
noDrag: true,
multiple: allowMultiple && (maxImageUploadCount === undefined || maxImageUploadCount > 1),

View File

@@ -1,5 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { selectMaxImageUploadCount } from 'features/system/store/configSlice';
import { t } from 'i18next';
import { PiUploadBold } from 'react-icons/pi';
@@ -7,14 +9,23 @@ const options = { postUploadAction: { type: 'TOAST' }, allowMultiple: true } as
export const GalleryUploadButton = () => {
const uploadApi = useImageUploadButton(options);
const maxImageUploadCount = useAppSelector(selectMaxImageUploadCount);
return (
<>
<IconButton
size="sm"
alignSelf="stretch"
variant="link"
aria-label={t('accessibility.uploadImages')}
tooltip={t('accessibility.uploadImages')}
aria-label={
maxImageUploadCount === undefined || maxImageUploadCount > 1
? t('accessibility.uploadImages')
: t('accessibility.uploadImage')
}
tooltip={
maxImageUploadCount === undefined || maxImageUploadCount > 1
? t('accessibility.uploadImages')
: t('accessibility.uploadImage')
}
icon={<PiUploadBold />}
{...uploadApi.getUploadButtonProps()}
/>

View File

@@ -43,7 +43,7 @@ export const ShareWorkflowModal = () => {
if (!workflowToShare || !projectUrl) {
return null;
}
return `${window.location.origin}/${projectUrl}/studio?selectedWorkflowId=${workflowToShare.workflow_id}`;
return `${window.location.origin}${projectUrl}/studio?selectedWorkflowId=${workflowToShare.workflow_id}`;
}, [projectUrl, workflowToShare]);
const handleCopy = useCallback(() => {

View File

@@ -1 +1 @@
__version__ = "5.2.0rc2"
__version__ = "5.2.0"