From cd32e37a174653ff61ec86a8ae58f42116e23904 Mon Sep 17 00:00:00 2001 From: erinz Date: Thu, 12 Oct 2023 17:05:13 +0000 Subject: [PATCH 01/17] sightings metadata redesign --- src/pages/sighting/AnnotationDetail.jsx | 128 ++++---- src/pages/sighting/Annotations.jsx | 308 +++++++++++++------- src/pages/sighting/MoreAnnotationMenu.jsx | 7 +- src/pages/sighting/SightingCore.jsx | 7 +- src/pages/sighting/SightingEntityHeader.jsx | 8 +- 5 files changed, 280 insertions(+), 178 deletions(-) diff --git a/src/pages/sighting/AnnotationDetail.jsx b/src/pages/sighting/AnnotationDetail.jsx index a6ffa13df..37e4fd280 100644 --- a/src/pages/sighting/AnnotationDetail.jsx +++ b/src/pages/sighting/AnnotationDetail.jsx @@ -27,6 +27,7 @@ export default function AnnotationDetail({ onClose, refreshSightingData, }) { + const hasNoAnnotation = Boolean(annotation?.annotations); const intl = useIntl(); const [anchorEl, setAnchorEl] = useState(null); const [addingTag, setAddingTag] = useState(false); @@ -139,77 +140,78 @@ export default function AnnotationDetail({ deletable refreshSightingData={refreshSightingData} > - {addingTag ? ( -
- { - setNewTagSelectValue(newValue); - }} - inputValue={newTagInputValue} - onInputChange={(_, newValue) => { - if (newValue) setNewTagInputValue(newValue); - }} - disabled={addKeywordLoading} - options={filteredKeywordOptions} - getOptionLabel={option => - get(option, 'value', '') - } - renderOption={option => ( -
+ {!hasNoAnnotation && + (addingTag ? ( +
+ { + setNewTagSelectValue(newValue); + }} + inputValue={newTagInputValue} + onInputChange={(_, newValue) => { + if (newValue) setNewTagInputValue(newValue); + }} + disabled={addKeywordLoading} + options={filteredKeywordOptions} + getOptionLabel={option => + get(option, 'value', '') + } + renderOption={option => (
+
+ {option.value} +
+ )} + renderInput={params => ( + - {option.value} -
- )} - renderInput={params => ( - - )} - /> + )} + /> +
+ ) : (
- ) : ( -
({ ...a, + asset, ...omit(asset, ['annotations', 'guid']), })); return [...acc, ...amendedAssetAnnotations]; }, []); + const noAnnotations = assets + .filter(data => data.annotations.length === 0) + .map(a => { + a.asset = a; + return a; + }); const clickedAnnotationId = get(anchorInfo, ['annotation', 'guid']); - + const hasNoAnnotation = get(anchorInfo, [ + 'annotation', + 'annotations', + ]); const detailAnnotation = annotations.find(a => a.guid === detailId); - const editAnnotation = annotations.find(a => a.guid === editId); + const detailNoAnnotation = noAnnotations.find( + a => a.guid === detailId, + ); + const editAnnotation = annotations.find(a => a.guid === editId); return ( -
- setMatchId(null)} - /> - setDetailId(null)} - refreshSightingData={refreshSightingData} + <> + setShowAnnotations(e.target.checked)} + /> + } + label={intl.formatMessage({ id: 'SHOW_ANNOTATIONS' })} /> - setAnchorInfo(null)} - onClickStartIdentification={() => { - setMatchId(clickedAnnotationId); - setAnchorInfo(null); - }} - onClickEditAnnotation={() => { - setEditId(clickedAnnotationId); - setAnchorInfo(null); - }} - onClickDelete={() => { - setDeleteId(clickedAnnotationId); - setAnchorInfo(null); + +
- {editId && ( - { - setEditId(null); - setAnnotationError(null); + > + setMatchId(null)} + /> + + {newAnnotationAsset && ( + setNewAnnotationAsset(null)} + asset={newAnnotationAsset} + sightingData={sightingData} + pending={pending} + /> + )} + setDetailId(null)} + refreshSightingData={refreshSightingData} + /> + setAnchorInfo(null)} + onClickStartIdentification={() => { + setMatchId(clickedAnnotationId); + setAnchorInfo(null); }} - onChange={async rect => { - const coords = [ - get(rect, 'percentLeft'), - get(rect, 'percentTop'), - get(rect, 'percentWidth'), - get(rect, 'percentHeight'), - ]; - const updateSuccessful = await updateAnnotationProperty( - editId, - 'bounds', - { rect: coords, theta: get(rect, 'theta', 0) }, + onClickEditAnnotation={() => { + setEditId(clickedAnnotationId); + setAnchorInfo(null); + }} + onClickAddAnnotation={() => { + setAnchorInfo(null); + setNewAnnotationAsset( + get(anchorInfo, ['annotation', 'asset']), ); - if (updateSuccessful) { + }} + onClickDelete={() => { + setDeleteId(clickedAnnotationId); + setAnchorInfo(null); + }} + /> + {editId && ( + { setEditId(null); + setAnnotationError(null); + }} + onChange={async rect => { + const coords = [ + get(rect, 'percentLeft'), + get(rect, 'percentTop'), + get(rect, 'percentWidth'), + get(rect, 'percentHeight'), + ]; + const updateSuccessful = await updateAnnotationProperty( + editId, + 'bounds', + { rect: coords, theta: get(rect, 'theta', 0) }, + ); + if (updateSuccessful) { + setEditId(null); + refreshSightingData(); + } + }} + disableDelete + error={patchError} + loading={patchInProgress} + imgSrc={get(editAnnotation, 'src')} + annotations={[editAnnotation]} + /> + )} + setDeleteId(null)} + onDelete={async () => { + const deleteSuccessful = await deleteAnnotation(deleteId); + if (deleteSuccessful) { + setDeleteId(null); refreshSightingData(); } }} - disableDelete - error={patchError} - loading={patchInProgress} - imgSrc={get(editAnnotation, 'src')} - annotations={[editAnnotation]} + deleteInProgress={deleteInProgress} + error={deleteAnnotationError} + onClearError={() => deleteAnnotationError(null)} + messageId="CONFIRM_DELETE_ANNOTATION_DESCRIPTION" /> - )} - setDeleteId(null)} - onDelete={async () => { - const deleteSuccessful = await deleteAnnotation(deleteId); - if (deleteSuccessful) { - setDeleteId(null); - refreshSightingData(); - } - }} - deleteInProgress={deleteInProgress} - error={deleteAnnotationError} - onClearError={() => deleteAnnotationError(null)} - messageId="CONFIRM_DELETE_ANNOTATION_DESCRIPTION" - /> - {annotations.length === 0 ? ( - - ) : null} - {annotations.map(annotation => ( -
- setDetailId(annotation.guid)} - /> - - setAnchorInfo({ element: e.currentTarget, annotation }) - } - style={{ - position: 'absolute', - top: 4, - right: 4, - color: theme.palette.common.white, - }} - className={classes.photoIcon} - > - - - -
- ))} -
+ {annotations.length === 0 ? ( + + ) : null} + + {annotations.map(annotation => ( +
+ { + setDetailId(annotation.guid); + }} + /> + { + setAnchorInfo({ + element: e.currentTarget, + annotation, + }); + }} + style={{ + position: 'absolute', + top: 4, + right: 4, + color: theme.palette.common.white, + }} + className={classes.photoIcon} + > + + + + {formatFilename(annotation.asset.filename, 35)} + + +
+ ))} + + {noAnnotations.map(annotation => ( +
+ { + setDetailId(annotation.guid); + }} + /> + { + setAnchorInfo({ + element: e.currentTarget, + annotation, + }); + }} + style={{ + position: 'absolute', + top: 4, + right: 4, + color: theme.palette.common.white, + }} + className={classes.photoIcon} + > + + + + {formatFilename(annotation.filename, 35)} + + +
+ ))} +
+ ); } diff --git a/src/pages/sighting/MoreAnnotationMenu.jsx b/src/pages/sighting/MoreAnnotationMenu.jsx index 28131ebc3..50f9715c9 100644 --- a/src/pages/sighting/MoreAnnotationMenu.jsx +++ b/src/pages/sighting/MoreAnnotationMenu.jsx @@ -13,6 +13,8 @@ export default function MoreAnnotationMenu({ // onClickStartIdentification = Function.prototype, // onClickEditAnnotation = Function.prototype, onClickDelete = Function.prototype, + onClickAddAnnotation = Function.prototype, + disable = false, }) { return ( @@ -25,7 +27,10 @@ export default function MoreAnnotationMenu({ > */} - + + + + diff --git a/src/pages/sighting/SightingCore.jsx b/src/pages/sighting/SightingCore.jsx index 240f9ae5d..129ef739e 100644 --- a/src/pages/sighting/SightingCore.jsx +++ b/src/pages/sighting/SightingCore.jsx @@ -19,7 +19,7 @@ import useDeleteAssetGroupSighting from '../../models/assetGroupSighting/useDele import useSightingFieldSchemas from '../../models/sighting/useSightingFieldSchemas'; import SightingEntityHeader from './SightingEntityHeader'; import Annotations from './Annotations'; -import Photographs from './Photographs'; +// import Photographs from './Photographs'; import OverviewContent from './OverviewContent'; // import SightingHistoryDialog from './SightingHistoryDialog'; import CommitBanner from './CommitBanner'; @@ -202,17 +202,18 @@ export default function SightingCore({ refreshSightingData={refreshData} /> )} - {activeTab === '#photographs' && ( + {/* {activeTab === '#photographs' && ( - )} + )} */} {activeTab === '#annotations' && ( )} diff --git a/src/pages/sighting/SightingEntityHeader.jsx b/src/pages/sighting/SightingEntityHeader.jsx index 456d31569..f99e64fc9 100644 --- a/src/pages/sighting/SightingEntityHeader.jsx +++ b/src/pages/sighting/SightingEntityHeader.jsx @@ -22,10 +22,10 @@ const tabItems = [ labelId: 'OVERVIEW', value: 'overview', }, - { - labelId: 'PHOTOGRAPHS', - value: 'photographs', - }, + // { + // labelId: 'PHOTOGRAPHS', + // value: 'photographs', + // }, { labelId: 'ANNOTATIONS', value: 'annotations', From b1b5d69a3ac8c17f954379f27e5d6cd5716e9fd5 Mon Sep 17 00:00:00 2001 From: erinz Date: Fri, 13 Oct 2023 19:52:00 +0000 Subject: [PATCH 02/17] match status --- locale/en.json | 9 +- src/components/dialogs/ReviewSighting.jsx | 82 ++++++++++++++ .../dialogs/ReviewSightingDialog.jsx | 65 ----------- src/models/sighting/useReviewSighting.js | 8 +- src/pages/match/MatchSighting.jsx | 103 ++++++++---------- src/pages/sighting/SightingEntityHeader.jsx | 39 +++---- 6 files changed, 152 insertions(+), 154 deletions(-) create mode 100644 src/components/dialogs/ReviewSighting.jsx delete mode 100644 src/components/dialogs/ReviewSightingDialog.jsx diff --git a/locale/en.json b/locale/en.json index 29de78b7c..ddaeb1a23 100644 --- a/locale/en.json +++ b/locale/en.json @@ -765,8 +765,8 @@ "SERVER_STATUS_PAGE_TITLE": "Server Status", "MATCH_ANNOTATIONS": "Match annotations", "MATCH_REVIEW": "Match review", - "MARK_SIGHTING_REVIEWED": "Mark sighting reviewed", - "MARK_SIGHTING_REVIEWED_CONFIRMATION": "Are you sure you want to mark this sighting reviewed? This action cannot be undone.", + "UPDATE_SIGHTING_STATUS": "Update sighting status", + "REVIEW_SIGHTING_CONFIRMATION": "Are you sure you want to mark this sighting as {match_status}?", "MATCH_COUNT": "{matchCount} {matchCount, plural, =0 {matches} one {match} other {matches}}", "PHOTO_COUNT": "{photoCount} {photoCount, plural, =0 {photographs} one {photograph} other {photographs}}", "ENCOUNTERS_IMPORTED_COUNT": "{encounterCount} {encounterCount, plural, =0 {encounters} one {encounter} other {encounters}} imported.", @@ -1264,5 +1264,8 @@ "ENCOUNTER_SEX" : "Encounter sex", "ENCOUNTER_SPECIES" : "Encounter species", "LAST_REGION" : "Last region", - "LAST_FREEFORM" : "Last freeform" + "LAST_FREEFORM" : "Last freeform", + "UNREVIEWED" : "Unreviewed", + "REVIEWED" : "Reviewed", + "IN_PROGRESS" : "In progress" } diff --git a/src/components/dialogs/ReviewSighting.jsx b/src/components/dialogs/ReviewSighting.jsx new file mode 100644 index 000000000..3831adf56 --- /dev/null +++ b/src/components/dialogs/ReviewSighting.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import useReviewSighting from '../../models/sighting/useReviewSighting'; +import Text from '../Text'; +import Alert from '../Alert'; +import ButtonMenu from '../ButtonMenu'; + +export default function ReviewSighting({ + sightingGuid, + matchStatus, +}) { + const { + mutate: reviewSighting, + error, + clearError, + } = useReviewSighting(); + + const sendRequest = async status => { + const operations = [ + { + op: 'replace', + path: '/match_state', + value: status, + }, + ]; + await reviewSighting({ sightingGuid, operations }); + }; + + const buttonActions = [ + { + id: 'mark-sighting-unreviewed', + labelId: 'UNREVIEWED', + onClick: async () => { + sendRequest('unreviewed'); + }, + }, + { + id: 'mark-sighting-in-progress', + labelId: 'IN_PROGRESS', + onClick: async () => { + sendRequest('in_progress'); + }, + }, + { + id: 'mark-sighting-reviewed', + labelId: 'REVIEWED', + onClick: async () => { + sendRequest('reviewed'); + }, + }, + { + id: 'mark-sighting-unidentifiable', + labelId: 'UNIDENTIFIABLE', + onClick: async () => { + sendRequest('unidentifiable'); + }, + }, + ]; + + return ( +
+ + + {error && ( + + {error} + + )} +
+ ); +} diff --git a/src/components/dialogs/ReviewSightingDialog.jsx b/src/components/dialogs/ReviewSightingDialog.jsx deleted file mode 100644 index fe2066d88..000000000 --- a/src/components/dialogs/ReviewSightingDialog.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; - -import DialogContent from '@material-ui/core/DialogContent'; -import DialogActions from '@material-ui/core/DialogActions'; - -import useReviewSighting from '../../models/sighting/useReviewSighting'; -import StandardDialog from '../StandardDialog'; -import Button from '../Button'; -import Text from '../Text'; -import Alert from '../Alert'; - -export default function ReviewSightingDialog({ - sightingGuid, - open, - onClose, -}) { - const { - mutate: reviewSighting, - loading, - error, - clearError, - } = useReviewSighting(); - - function handleClose() { - clearError(); - onClose(); - } - - return ( - - - - {error && ( - - {error} - - )} - - -
+ )} {matchPossible && ( @@ -299,6 +334,7 @@ export default function MatchSighting() { color="primary" variant="extended" href={confirmMatchHref} + onClick={() => setNoMatch(false)} > From cfe544e7bde20aeb991a5eebca51b27eb76046c8 Mon Sep 17 00:00:00 2001 From: erinz Date: Mon, 23 Oct 2023 16:01:43 +0000 Subject: [PATCH 09/17] add more matching cases --- locale/en.json | 4 +++- src/pages/match/MatchSighting.jsx | 32 +++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/locale/en.json b/locale/en.json index d0d2dc217..1dacc8e26 100644 --- a/locale/en.json +++ b/locale/en.json @@ -1266,5 +1266,7 @@ "UNREVIEWED" : "Unreviewed", "REVIEWED" : "Reviewed", "IN_PROGRESS" : "In progress", - "UNPROCESSED" : "Unprocessed" + "UNPROCESSED" : "Unprocessed", + "NO_MATCH_DESCRIPTION" : "Can't find any match for the sighting?", + "CONFIRM_NO_MATCH" : "CONFIRM NO MATCH" } diff --git a/src/pages/match/MatchSighting.jsx b/src/pages/match/MatchSighting.jsx index 96e81483c..a7da561dc 100644 --- a/src/pages/match/MatchSighting.jsx +++ b/src/pages/match/MatchSighting.jsx @@ -126,10 +126,7 @@ export default function MatchSighting() { }); }, [matchResults, selectedQueryAnnotation]); - console.log(noMatch); - const confirmMatchHref = useMemo(() => { - console.log(noMatch); const individualGuid1 = deriveIndividualGuid( selectedQueryAnnotation, ); @@ -164,21 +161,22 @@ export default function MatchSighting() { }; checkHeatMap(); - if (individualGuid1 && individualGuid2) { - return `/merge?i=${individualGuid1}&i=${individualGuid2}`; - } else if (individualGuid1 || individualGuid2) { - const individualGuid = individualGuid1 || individualGuid2; - const encounterGuid = individualGuid1 - ? encounterGuid2 - : encounterGuid1; - return `/assign-annotations?i=${individualGuid}&e=${encounterGuid}`; - } else { - console.log(noMatch); - if (noMatch) { - return `/create-individual?e=${encounterGuid1}`; + if (!noMatch) { + if (individualGuid1 && individualGuid2) { + return `/merge?i=${individualGuid1}&i=${individualGuid2}`; + } else if (individualGuid1 || individualGuid2) { + const individualGuid = individualGuid1 || individualGuid2; + const encounterGuid = individualGuid1 + ? encounterGuid2 + : encounterGuid1; + return `/assign-annotations?i=${individualGuid}&e=${encounterGuid}`; } else { return `/create-individual?e=${encounterGuid1}&e=${encounterGuid2}`; } + } else if (!individualGuid1) { + return `/create-individual?e=${encounterGuid1}`; + } else { + return null; } }, [selectedQueryAnnotation, selectedMatchCandidate, noMatch]); @@ -309,8 +307,8 @@ export default function MatchSighting() { > } > Date: Thu, 26 Oct 2023 15:45:05 +0000 Subject: [PATCH 16/17] change photographs to annotations --- src/pages/sighting/statusCard/CurationStep.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/sighting/statusCard/CurationStep.jsx b/src/pages/sighting/statusCard/CurationStep.jsx index 34fe91f37..bb9d61deb 100644 --- a/src/pages/sighting/statusCard/CurationStep.jsx +++ b/src/pages/sighting/statusCard/CurationStep.jsx @@ -119,7 +119,7 @@ export default function CurationStep({ sightingData }) { href={ someAssetsHaveAnnotations ? '#individuals' - : '#photographs' + : '#annotations' } display="primary" size="small" From de6980131b244b21bf9ed7b6b15ba6693002f16c Mon Sep 17 00:00:00 2001 From: erinz Date: Thu, 26 Oct 2023 19:21:12 +0000 Subject: [PATCH 17/17] change add to assign to keep consistent with assign button --- locale/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/en.json b/locale/en.json index 3376abb31..d21b17c66 100644 --- a/locale/en.json +++ b/locale/en.json @@ -32,8 +32,8 @@ "ASSIGN_ANNOTATIONS": "Assign annotations", "ASSIGN_ANNOTATIONS_TO_INDIVIDUAL": "Assign annotations to individual", "ASSIGN_ANNOTATIONS_TO_INDIVIDUAL_INSTRUCTIONS": "Verify that the annotations below should be assigned to this individual before proceeding.", - "ADD_ANNOTATIONS_TO_CLUSTER_TITLE": "Add annotations to animal", - "ADD_ANNOTATIONS_TO_CLUSTER_BUTTON": "Add annotations", + "ADD_ANNOTATIONS_TO_CLUSTER_TITLE": "Assign annotations to animal", + "ADD_ANNOTATIONS_TO_CLUSTER_BUTTON": "Assign annotations", "SIGHTING_COMMIT_TITLE": "Ready to commit", "REMOVE_FROM_CLUSTER": "Remove from animal", "MOVE_ANNOTATION": "Move annotation",