Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

138: Image level tags #252

Merged
merged 19 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/api/buildQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const imageFields = `
comments {
${imageCommentFields}
}
tags
reviewed
`;

Expand Down Expand Up @@ -152,6 +153,12 @@ const projectLabelFields = `
ml
`;

const projectTagFields = `
_id
name
color
`;

const projectFields = `
_id
name
Expand All @@ -169,6 +176,9 @@ const projectFields = `
labels {
${projectLabelFields}
}
tags {
${projectTagFields}
}
availableMLModels
`;

Expand Down Expand Up @@ -512,6 +522,45 @@ const queries = {
variables: { input: input },
}),

createProjectTag: (input) => ({
template: `
mutation CreateProjectTag($input: CreateProjectTagInput!) {
createProjectTag(input: $input) {
tags {
${projectTagFields}
}
}
}
`,
variables: { input: input },
}),

deleteProjectTag: (input) => ({
template: `
mutation DeleteProjectTag($input: DeleteProjectTagInput!) {
deleteProjectTag(input: $input) {
tags {
${projectTagFields}
}
}
}
`,
variables: { input: input },
}),

updateProjectTag: (input) => ({
template: `
mutation UpdateProjectTag($input: UpdateProjectTagInput!) {
updateProjectTag(input: $input) {
tags {
${projectTagFields}
}
}
}
`,
variables: { input: input },
}),

createProjectLabel: (input) => ({
template: `
mutation CreateProjectLabel($input: CreateProjectLabelInput!) {
Expand Down Expand Up @@ -587,6 +636,28 @@ const queries = {
`,
variables: { input: input },
}),

createImageTag: (input) => ({
template: `
mutation CreateImageTag($input: CreateImageTagInput!) {
createImageTag(input: $input) {
tags
}
}
`,
variables: { input: input },
}),

deleteImageTag: (input) => ({
template: `
mutation DeleteImageTag($input: DeleteImageTagInput!) {
deleteImageTag(input: $input) {
tags
}
}
`,
variables: { input: input },
}),

createDeployment: (input) => ({
template: `
Expand Down
6 changes: 4 additions & 2 deletions src/assets/fontawesome.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ import {
faUpload,
faUser,
faTag,
faRetweet
faRetweet,
faHighlighter
} from '@fortawesome/free-solid-svg-icons';

library.add(
Expand Down Expand Up @@ -62,5 +63,6 @@ library.add(
faUpload,
faUser,
faTag,
faRetweet
faRetweet,
faHighlighter
);
10 changes: 10 additions & 0 deletions src/components/ErrorToast.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
dismissLabelsError,
selectCommentsErrors,
dismissCommentsError,
selectTagsErrors,
dismissTagsError,
} from '../features/review/reviewSlice';
import {
selectProjectsErrors,
Expand All @@ -20,6 +22,8 @@ import {
dismissCreateProjectError,
selectManageLabelsErrors,
dismissManageLabelsError,
selectProjectTagErrors,
dismissProjectTagErrors
} from '../features/projects/projectsSlice';
import {
selectWirelessCamerasErrors,
Expand Down Expand Up @@ -57,6 +61,7 @@ import { selectManageUserErrors, dismissManageUsersError } from '../features/pro
const ErrorToast = () => {
const dispatch = useDispatch();
const labelsErrors = useSelector(selectLabelsErrors);
const tagsErrors = useSelector(selectTagsErrors);
const commentsErrors = useSelector(selectCommentsErrors);
const projectsErrors = useSelector(selectProjectsErrors);
const viewsErrors = useSelector(selectViewsErrors);
Expand All @@ -74,9 +79,12 @@ const ErrorToast = () => {
const manageLabelsErrors = useSelector(selectManageLabelsErrors);
const uploadErrors = useSelector(selectUploadErrors);
const cameraSerialNumberErrors = useSelector(selectCameraSerialNumberErrors);
const projectTagErrors = useSelector(selectProjectTagErrors);

const enrichedErrors = [
enrichErrors(labelsErrors, 'Label Error', 'labels'),
enrichErrors(tagsErrors, 'Tag Error', 'tags'),
enrichErrors(projectTagErrors, 'Tag Error', 'projectTags'),
enrichErrors(commentsErrors, 'Comment Error', 'comments'),
enrichErrors(projectsErrors, 'Project Error', 'projects'),
enrichErrors(viewsErrors, 'View Error', 'views'),
Expand Down Expand Up @@ -144,6 +152,8 @@ const ErrorToast = () => {

const dismissErrorActions = {
labels: (i) => dismissLabelsError(i),
tags: (i) => dismissTagsError(i),
projectTags: (i) => dismissProjectTagErrors(i),
comments: (i) => dismissCommentsError(i),
projects: (i) => dismissProjectsError(i),
createProject: (i) => dismissCreateProjectError(i),
Expand Down
7 changes: 7 additions & 0 deletions src/components/HydratedModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
setSelectedCamera,
} from '../features/projects/projectsSlice';
import { clearUsers } from '../features/projects/usersSlice.js';
import { ManageTagsModal } from '../features/projects/ManageTagsModal/ManageTagsModal.jsx';

// Modal populated with content
const HydratedModal = () => {
Expand Down Expand Up @@ -121,6 +122,12 @@ const HydratedModal = () => {
dispatch(clearCameraSerialNumberTask());
},
},
'manage-tags-form': {
title: 'Manage tags',
size: 'md',
content: <ManageTagsModal />,
callBackOnClose: () => true,
}
};

const handleModalToggle = (content) => {
Expand Down
97 changes: 97 additions & 0 deletions src/components/TagSelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React from 'react';
import { styled } from '../theme/stitches.config.js';
import { useSelector, useDispatch } from 'react-redux';
import Select, { createFilter } from 'react-select';
import {
selectTagsLoading
} from '../features/projects/projectsSlice.js';
import { addLabelEnd } from '../features/loupe/loupeSlice.js';

const StyledTagSelector = styled(Select, {
width: '155px',
fontFamily: '$mono',
fontSize: '$2',
fontWeight: '$1',
zIndex: '$5',
'.react-select__control': {
boxSizing: 'border-box',
// height: '24px',
minHeight: 'unset',
border: '1px solid',
borderColor: '$border',
borderRadius: '$2',
cursor: 'pointer',
},
'.react-select__single-value': {
// position: 'relative',
},
'.react-select__indicator-separator': {
display: 'none',
},
'.react-select__dropdown-indicator': {
paddingTop: '0',
paddingBottom: '0',
},
'.react-select__control--is-focused': {
transition: 'all 0.2s ease',
boxShadow: '0 0 0 3px $blue200',
borderColor: '$blue500',
'&:hover': {
boxShadow: '0 0 0 3px $blue200',
borderColor: '$blue500',
},
},
'.react-select__menu': {
color: '$textDark',
fontSize: '$3',
'.react-select__option': {
cursor: 'pointer',
},
'.react-select__option--is-selected': {
color: '$blue500',
backgroundColor: '$blue200',
},
'.react-select__option--is-focused': {
backgroundColor: '$gray3',
},
},
});

export const TagSelector = ({
css,
tags,
handleTagChange,
handleTagChangeBlur,
menuPlacement = 'top',
}) => {
const tagsLoading = useSelector(selectTagsLoading);
const options = tags.map((tag) => {
return {
value: tag._id,
label: tag.name
}
});
const dispatch = useDispatch();
const defaultHandleBlur = () => dispatch(addLabelEnd());

return (
<StyledTagSelector
value={""}
css={css}
autoFocus
closeMenuOnSelect={options.length <= 1}
isClearable
isSearchable
openMenuOnClick
className="react-select"
classNamePrefix="react-select"
menuPlacement={menuPlacement}
filterOption={createFilter({ matchFrom: 'start' })}
isLoading={tagsLoading.isLoading}
isDisabled={tagsLoading.isLoading}
onChange={handleTagChange}
onBlur={handleTagChangeBlur || defaultHandleBlur}
options={options}
/>
);
};
95 changes: 95 additions & 0 deletions src/features/loupe/ImageTag.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';
import { styled } from '../../theme/stitches.config';
import {
Tooltip,
TooltipContent,
TooltipArrow,
TooltipTrigger,
} from '../../components/Tooltip.jsx';
import { Cross2Icon } from '@radix-ui/react-icons';
import Button from '../../components/Button.jsx';
import { violet, mauve } from '@radix-ui/colors';

export const itemStyles = {
all: 'unset',
flex: '0 0 auto',
color: mauve.mauve11,
height: 30,
padding: '0 5px',
borderRadius: '0 4px 4px 0',
display: 'flex',
fontSize: 13,
lineHeight: 1,
alignItems: 'center',
justifyContent: 'center',
'&:hover': {
backgroundColor: violet.violet3,
color: violet.violet11,
cursor: 'pointer',
},
'&:focus': { position: 'relative', boxShadow: `0 0 0 2px ${violet.violet7}` },
};

const ToolbarIconButton = styled(Button, {
...itemStyles,
backgroundColor: 'white',
'&:first-child': { marginLeft: 0 },
'&[data-state=on]': {
backgroundColor: violet.violet5,
color: violet.violet11,
},
svg: {
marginRight: '$1',
marginLeft: '$1',
},
});

const TagContainer = styled('div', {
display: 'flex',
border: '1px solid rgba(0,0,0,0)',
borderRadius: 4,
height: 32,
});

const TagName = styled('div', {
padding: '$1 $3',
color: '$textDark',
fontFamily: '$mono',
fontWeight: 'bold',
fontSize: '$2',
display: 'grid',
placeItems: 'center',
marginLeft: '0',
marginRight: 'auto',
height: 30,
borderRadius: '4px 0 0 4px'
});

export const ImageTag = ({
id,
name,
color,
onDelete
}) => {
return (
<TagContainer css={{
borderColor: color,
backgroundColor: `${color}1A`,
}}>
<TagName>
{ name }
</TagName>
<Tooltip>
<TooltipTrigger asChild>
<ToolbarIconButton onClick={() => onDelete(id)}>
<Cross2Icon />
</ToolbarIconButton>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={5}>
Delete tag
<TooltipArrow />
</TooltipContent>
</Tooltip>
</TagContainer>
);
}
Loading
Loading