Skip to content

Commit ae9cca7

Browse files
Copilotlstein
andcommitted
Fix remaining board access enforcement: invoke icon, drag-out, change-board filter, archive
Co-authored-by: lstein <111189+lstein@users.noreply.github.com>
1 parent a0b90b1 commit ae9cca7

4 files changed

Lines changed: 50 additions & 22 deletions

File tree

invokeai/frontend/web/src/features/changeBoardModal/components/ChangeBoardModal.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import {
88
isModalOpenChanged,
99
selectChangeBoardModalSlice,
1010
} from 'features/changeBoardModal/store/slice';
11+
import { selectCurrentUser } from 'features/auth/store/authSlice';
1112
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
1213
import { memo, useCallback, useMemo, useState } from 'react';
1314
import { useTranslation } from 'react-i18next';
1415
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
1516
import { useAddImagesToBoardMutation, useRemoveImagesFromBoardMutation } from 'services/api/endpoints/images';
17+
import type { BoardDTO } from 'services/api/types';
1618

1719
const selectImagesToChange = createSelector(
1820
selectChangeBoardModalSlice,
@@ -28,6 +30,7 @@ const ChangeBoardModal = () => {
2830
useAssertSingleton('ChangeBoardModal');
2931
const dispatch = useAppDispatch();
3032
const currentBoardId = useAppSelector(selectSelectedBoardId);
33+
const currentUser = useAppSelector(selectCurrentUser);
3134
const [selectedBoardId, setSelectedBoardId] = useState<string | null>();
3235
const { data: boards, isFetching } = useListAllBoardsQuery({ include_archived: true });
3336
const isModalOpen = useAppSelector(selectIsModalOpen);
@@ -36,18 +39,28 @@ const ChangeBoardModal = () => {
3639
const [removeImagesFromBoard] = useRemoveImagesFromBoardMutation();
3740
const { t } = useTranslation();
3841

42+
// Returns true if the current user can write images to the given board.
43+
const canWriteToBoard = useCallback(
44+
(board: BoardDTO): boolean => {
45+
const isOwnerOrAdmin = !currentUser || currentUser.is_admin || board.user_id === currentUser.user_id;
46+
return isOwnerOrAdmin || board.board_visibility === 'public';
47+
},
48+
[currentUser]
49+
);
50+
3951
const options = useMemo<ComboboxOption[]>(() => {
4052
return [{ label: t('boards.uncategorized'), value: 'none' }]
4153
.concat(
4254
(boards ?? [])
55+
.filter(canWriteToBoard)
4356
.map((board) => ({
4457
label: board.board_name,
4558
value: board.board_id,
4659
}))
4760
.sort((a, b) => a.label.localeCompare(b.label))
4861
)
4962
.filter((board) => board.value !== currentBoardId);
50-
}, [boards, currentBoardId, t]);
63+
}, [boards, canWriteToBoard, currentBoardId, t]);
5164

5265
const value = useMemo(() => options.find((o) => o.value === selectedBoardId), [options, selectedBoardId]);
5366

invokeai/frontend/web/src/features/gallery/components/Boards/BoardContextMenu.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,13 @@ const BoardContextMenu = ({ board, children }: Props) => {
130130
</MenuItem>
131131

132132
{board.archived && (
133-
<MenuItem icon={<PiArchiveBold />} onClick={handleUnarchive}>
133+
<MenuItem icon={<PiArchiveBold />} onClick={handleUnarchive} isDisabled={!canDeleteBoard}>
134134
{t('boards.unarchiveBoard')}
135135
</MenuItem>
136136
)}
137137

138138
{!board.archived && (
139-
<MenuItem icon={<PiArchiveFill />} onClick={handleArchive}>
139+
<MenuItem icon={<PiArchiveFill />} onClick={handleArchive} isDisabled={!canDeleteBoard}>
140140
{t('boards.archiveBoard')}
141141
</MenuItem>
142142
)}

invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImage.tsx

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import type { MouseEvent, MouseEventHandler } from 'react';
2626
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
2727
import { PiImageBold } from 'react-icons/pi';
2828
import { imagesApi } from 'services/api/endpoints/images';
29+
import { useBoardAccess } from 'services/api/hooks/useBoardAccess';
30+
import { useSelectedBoard } from 'services/api/hooks/useSelectedBoard';
2931
import type { ImageDTO } from 'services/api/types';
3032

3133
import { galleryItemContainerSX } from './galleryItemContainerSX';
@@ -102,12 +104,37 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
102104
[imageDTO.image_name]
103105
);
104106
const isSelected = useAppSelector(selectIsSelected);
107+
const selectedBoard = useSelectedBoard();
108+
const { canWriteImages: canDragFromBoard } = useBoardAccess(selectedBoard);
105109

106110
useEffect(() => {
107111
const element = ref.current;
108112
if (!element) {
109113
return;
110114
}
115+
116+
const monitorBinding = monitorForElements({
117+
// This is a "global" drag start event, meaning that it is called for all drag events.
118+
onDragStart: ({ source }) => {
119+
// When we start dragging multiple images, set the dragging state to true if the dragged image is part of the
120+
// selection. This is called for all drag events.
121+
if (
122+
multipleImageDndSource.typeGuard(source.data) &&
123+
source.data.payload.image_names.includes(imageDTO.image_name)
124+
) {
125+
setIsDragging(true);
126+
}
127+
},
128+
onDrop: () => {
129+
// Always set the dragging state to false when a drop event occurs.
130+
setIsDragging(false);
131+
},
132+
});
133+
134+
if (!canDragFromBoard) {
135+
return combine(firefoxDndFix(element), monitorBinding);
136+
}
137+
111138
return combine(
112139
firefoxDndFix(element),
113140
draggable({
@@ -153,25 +180,9 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
153180
}
154181
},
155182
}),
156-
monitorForElements({
157-
// This is a "global" drag start event, meaning that it is called for all drag events.
158-
onDragStart: ({ source }) => {
159-
// When we start dragging multiple images, set the dragging state to true if the dragged image is part of the
160-
// selection. This is called for all drag events.
161-
if (
162-
multipleImageDndSource.typeGuard(source.data) &&
163-
source.data.payload.image_names.includes(imageDTO.image_name)
164-
) {
165-
setIsDragging(true);
166-
}
167-
},
168-
onDrop: () => {
169-
// Always set the dragging state to false when a drop event occurs.
170-
setIsDragging(false);
171-
},
172-
})
183+
monitorBinding
173184
);
174-
}, [imageDTO, store]);
185+
}, [imageDTO, store, canDragFromBoard]);
175186

176187
const [isHovered, setIsHovered] = useState(false);
177188

invokeai/frontend/web/src/features/ui/components/FloatingLeftPanelButtons.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
PiXCircle,
1818
} from 'react-icons/pi';
1919
import { useGetQueueStatusQuery } from 'services/api/endpoints/queue';
20+
import { useAutoAddBoard } from 'services/api/hooks/useAutoAddBoard';
21+
import { useBoardAccess } from 'services/api/hooks/useBoardAccess';
2022

2123
export const FloatingLeftPanelButtons = memo(() => {
2224
return (
@@ -71,14 +73,16 @@ const InvokeIconButton = memo(() => {
7173
const { t } = useTranslation();
7274
const queue = useInvoke();
7375
const shift = useShiftModifier();
76+
const autoAddBoard = useAutoAddBoard();
77+
const { canWriteImages } = useBoardAccess(autoAddBoard);
7478

7579
return (
7680
<InvokeButtonTooltip prepend={shift} placement="end">
7781
<IconButton
7882
aria-label={t('queue.queueBack')}
7983
onClick={shift ? queue.enqueueFront : queue.enqueueBack}
8084
isLoading={queue.isLoading}
81-
isDisabled={queue.isDisabled}
85+
isDisabled={queue.isDisabled || !canWriteImages}
8286
icon={<InvokeIconButtonIcon />}
8387
colorScheme="invokeYellow"
8488
flexGrow={1}

0 commit comments

Comments
 (0)