+ onSettingsChange({
+ ...settings,
+ showPathCrops: !settings.showPathCrops,
+ })
+ }
+ />
+ ) : null}
{
{verifyOccurrenceId ? (
{
diff --git a/ui/src/utils/getAppRoute.ts b/ui/src/utils/getAppRoute.ts
index bafc8290c..461830291 100644
--- a/ui/src/utils/getAppRoute.ts
+++ b/ui/src/utils/getAppRoute.ts
@@ -8,6 +8,7 @@ type FilterType =
| 'deployment__research_site'
| 'detections__source_image'
| 'event'
+ | 'extend'
| 'include_unobserved'
| 'occurrence'
| 'source_image_collection'
diff --git a/ui/src/utils/language.ts b/ui/src/utils/language.ts
index feb83bed6..2b3f6908a 100644
--- a/ui/src/utils/language.ts
+++ b/ui/src/utils/language.ts
@@ -12,6 +12,8 @@ export enum STRING {
COLLAPSE,
CONFIRM,
CONFIRMED,
+ COPIED,
+ COPY_LINK,
CREATE_NEW,
CURRENT_LOCATION,
DELETE,
@@ -26,8 +28,12 @@ export enum STRING {
MORE,
MORE_FILTERS,
NEXT,
+ NEXT_CAPTURE,
+ NEXT_CAPTURE_WITH_DETECTIONS,
POPULATE,
PREVIOUS,
+ PREVIOUS_CAPTURE,
+ PREVIOUS_CAPTURE_WITH_DETECTIONS,
PROCESS_NOW,
QUEUED,
REFRESH,
@@ -72,6 +78,7 @@ export enum STRING {
ENTITY_TYPE_IDENTIFICATION,
ENTITY_TYPE_JOB,
ENTITY_TYPE_MEMBER,
+ ENTITY_TYPE_OCCURRENCE,
ENTITY_TYPE_PIPELINE,
ENTITY_TYPE_PROCESSING_SERVICE,
ENTITY_TYPE_PROJECT_DETAILS,
@@ -85,6 +92,7 @@ export enum STRING {
FIELD_LABEL_ALGORITHM_URI,
FIELD_LABEL_AVG_TEMP,
FIELD_LABEL_BEST_SCORE,
+ FIELD_LABEL_BOXES_WITH_VECTORS,
FIELD_LABEL_CAPTURE_SET,
FIELD_LABEL_CAPTURE,
FIELD_LABEL_CAPTURES_WITH_DETECTIONS,
@@ -102,6 +110,7 @@ export enum STRING {
FIELD_LABEL_DELAY,
FIELD_LABEL_DEPLOYMENT,
FIELD_LABEL_DESCRIPTION,
+ FIELD_LABEL_DETECTIONS,
FIELD_LABEL_DEVICE,
FIELD_LABEL_DIRECT_OCCURRENCES,
FIELD_LABEL_DURATION,
@@ -118,6 +127,7 @@ export enum STRING {
FIELD_LABEL_GENERAL,
FIELD_LABEL_ICON,
FIELD_LABEL_ID,
+ FIELD_LABEL_ID_AGREEMENT,
FIELD_LABEL_IMAGE,
FIELD_LABEL_JOB_STATUS,
FIELD_LABEL_JOB,
@@ -133,9 +143,12 @@ export enum STRING {
FIELD_LABEL_LOGS,
FIELD_LABEL_LONGITUDE,
FIELD_LABEL_MOST_RECENT,
+ FIELD_LABEL_MOTION,
FIELD_LABEL_NAME,
FIELD_LABEL_NEW_PASSWORD,
FIELD_LABEL_NUM_PIPELINES_REGISTERED,
+ FIELD_LABEL_DETECTION_ID,
+ FIELD_LABEL_OCCURRENCE_NUMBER,
FIELD_LABEL_OCCURRENCES,
FIELD_LABEL_PASSWORD_CURRENT,
FIELD_LABEL_PASSWORD_NEW,
@@ -152,9 +165,11 @@ export enum STRING {
FIELD_LABEL_SAMPLING_METHOD,
FIELD_LABEL_SCORE,
FIELD_LABEL_SESSION,
+ FIELD_LABEL_SESSION_NUMBER,
FIELD_LABEL_SESSIONS,
FIELD_LABEL_SITE,
FIELD_LABEL_SIZE,
+ FIELD_LABEL_SIZE_CHANGE,
FIELD_LABEL_SLUG,
FIELD_LABEL_SNAPSHOTS,
FIELD_LABEL_STARTED_AT,
@@ -356,7 +371,7 @@ export enum STRING {
SET_PASSWORD,
SETTINGS,
SHOW_DETECTIONS,
- SNAP_TO_DETECTIONS,
+ SHOW_PATH_CROPS,
SORT_BY,
SORT_JOBS_ACTIVITY,
SORT_OCCURRENCE_UPDATES,
@@ -366,12 +381,215 @@ export enum STRING {
SUMMARY,
TABLE_COLUMNS,
TERMINAL_CLASSIFICATION,
+ TIMELINE_OCCURRENCE_FRAME,
+ TIMELINE_OCCURRENCE_SPAN,
+ TIMELINE_DURATION_SECONDS,
+ TIMELINE_DURATION_MINUTES,
+ TIMELINE_DURATION_HOURS,
+ TIMELINE_ACTIVITY_DESCRIPTION,
+ TIMELINE_ACTIVITY_LABEL,
+ TIMELINE_OCCURRENCE_ACTIVATE,
+ TIMELINE_OCCURRENCE_SHOWING,
+ TIMELINE_POSITION_LABEL,
+ TIMELINE_POSITION_VALUE,
TIP,
+ TRACK_BOX_EXTEND_ACTION,
+ TRACK_BOX_EXTEND_LABEL,
+ TRACK_BOX_LABEL,
+ TRACK_BOX_LABEL_VERIFIED,
+ TRACK_CANDIDATE_IN_GAP,
+ TRACK_CLEARS_CONFIRMATION,
+ TRACK_COLUMN_COST,
+ TRACK_COLUMN_COST_HELP,
+ TRACK_COLUMN_DISTANCE,
+ TRACK_COLUMN_FRAMES,
+ TRACK_COLUMN_IMAGE,
+ TRACK_COLUMN_MATCH,
+ TRACK_COLUMN_MATCH_HELP,
+ TRACK_COLUMN_OVERLAP,
+ TRACK_COLUMN_SIMILARITY,
+ TRACK_COLUMN_SIZE,
+ TRACK_COLUMN_SPECIES,
+ TRACK_COLUMN_WHEN,
+ TRACK_COST_OF_THRESHOLD,
+ TRACK_VIEW_CANDIDATE_IN_SESSION,
+ TRACK_CONFIRM_GROUPING_DESCRIPTION,
+ TRACK_CONFIRM_GROUPING_RESULT,
+ TRACK_CONFIRM_GROUPING,
+ TRACK_GROUPING_FILTER_NO,
+ TRACK_GROUPING_FILTER_TOOLTIP,
+ TRACK_GROUPING_FILTER_YES,
+ TRACK_EXTEND_CAPTURE_COVERED,
+ TRACK_EXTEND_CHOICE_DESCRIPTION,
+ TRACK_EXTEND_CLICKED_FRAME,
+ TRACK_EXTEND_DETAILS,
+ TRACK_EXTEND_HERE,
+ TRACK_EXTEND_HIDE_DETAILS,
+ TRACK_EXTEND_IN_SESSION,
+ TRACK_EXTEND_LABEL,
+ TRACK_EXTEND_LATEST_FRAME,
+ TRACK_EXTEND_MERGE_OVERLAPS,
+ TRACK_EXTEND_MERGE_WHOLE,
+ TRACK_EXTEND_MOVE_FRAME,
+ TRACK_EXTEND_NO_LATER_CAPTURE,
+ TRACK_EXTEND_ONLY_FRAME,
+ TRACK_EXTEND_REMOVE,
+ TRACK_EXTEND_REMOVE_PROMPT,
+ TRACK_EXTEND_REPLACE,
+ TRACK_EXTEND_REPLACE_ORPHAN,
+ TRACK_EXTEND_REPLACE_PROMPT,
+ TRACK_EXTEND_SHOW_DETAILS,
+ TRACK_EXTEND_STOP,
+ TRACK_EXTEND_TITLE,
+ TRACK_FIRST_FRAME,
+ TRACK_FRAMES_COUNT,
+ TRACK_FRAMES_ONE,
+ TRACK_FRAMES_PAGE_EARLIER,
+ TRACK_FRAMES_PAGE_ERROR,
+ TRACK_FRAMES_PAGE_FIRST,
+ TRACK_FRAMES_PAGE_LAST,
+ TRACK_FRAMES_PAGE_LATER,
+ TRACK_FRAMES_RANGE,
+ TRACK_FRAME_DETECTION,
+ TRACK_FRAME_IN_TRACK,
+ TRACK_FRAME_MENU,
+ TRACK_FRAME_NO_CLASSIFICATION,
+ TRACK_FRAME_NO_CROP,
+ TRACK_FRAME_NO_VECTOR,
+ TRACK_FRAME_THIS_CAPTURE,
+ TRACK_HIDE_PANEL,
+ TRACK_HIDE_PATH,
+ TRACK_JUMP_FIRST_FRAME,
+ TRACK_JUMP_LAST_FRAME,
+ TRACK_JUMP_ONLY_FRAME,
+ TRACK_LAST_FRAME,
+ TRACK_LOADING_PATH,
+ TRACK_GAP_AFTER,
+ TRACK_GAP_BEFORE,
+ TRACK_GHOST_EARLIER,
+ TRACK_GHOST_HINT,
+ TRACK_GHOST_LATER,
+ TRACK_GROUPING_CONFIRMED_BY,
+ TRACK_GROUPING_CONFIRMED,
+ TRACK_GROUPING_NOT_CONFIRMED,
+ TRACK_GROUPING_UNCONFIRMED,
+ TRACK_MATCH_CLICK_ADD,
+ TRACK_MATCH_CLICK_CHOOSE,
+ TRACK_MATCH_CLICK_REMOVE,
+ TRACK_MATCH_CLICK_REPLACE,
+ TRACK_MATCH_LABEL,
+ TRACK_MATCH_LIKELY,
+ TRACK_MATCH_POSSIBLE,
+ TRACK_MATCH_REFERENCE_EARLIER,
+ TRACK_MATCH_REFERENCE_LATER,
+ TRACK_MATCH_SAME_SPECIES,
+ TRACK_MATCH_SCORE,
+ TRACK_MATCH_SKIPPED_NO_VECTOR,
+ TRACK_MATCH_SKIPPED_SESSION,
+ TRACK_MATCH_UNLIKELY,
+ TRACK_MATCH_WOULD_LINK,
+ TRACK_MERGE_CANDIDATES_LOAD_FAILED,
+ TRACK_MERGE_CANDIDATES_TITLE,
+ TRACK_MERGE_COUNT,
+ TRACK_MERGE_MANY_DESCRIPTION,
+ TRACK_MERGE_ONE,
+ TRACK_MERGE_SCOPE_DESCRIPTION,
+ TRACK_MERGE_RESULT,
+ TRACK_MERGE,
+ TRACK_MOVE_DESCRIPTION,
+ TRACK_MOVE_SCOPE_DESCRIPTION,
+ TRACK_MOVE_FRAME,
+ TRACK_MOVE_RESULT,
+ TRACK_NEXT_FRAME,
+ TRACK_NO_MERGE_CANDIDATES_SCOPE,
+ TRACK_OFFSET_HOURS,
+ TRACK_OFFSET_MINUTES,
+ TRACK_OFFSET_SECONDS,
+ TRACK_OPEN_NEW_OCCURRENCE,
+ TRACK_PERCENT,
+ TRACK_PATH_ERROR,
+ TRACK_PATH_RANGE,
+ TRACK_PATH_STATUS_ERROR,
+ TRACK_PATH_STATUS_LOADING,
+ TRACK_PATH_STATUS_SHOWN,
+ TRACK_PATH_STATUS_SHOWN_FOR,
+ TRACK_PATH_OCCURRENCE,
+ TRACK_PATH_GO_TO_FRAME,
+ TRACK_PATH_GO_TO_NEAREST,
+ TRACK_PICK_OCCURRENCE,
+ TRACK_POSITION_AFTER_LAST,
+ TRACK_POSITION_BEFORE_FIRST,
+ TRACK_POSITION_BETWEEN,
+ TRACK_POSITION_FRAME,
+ TRACK_PREVIOUS_FRAME,
+ TRACK_REMOVE_DESCRIPTION,
+ TRACK_REMOVE_FRAME,
+ TRACK_REMOVE_RESULT,
+ TRACK_SCOPE_LABEL,
+ TRACK_SCOPE_MINUTES_30,
+ TRACK_SCOPE_MINUTES_5,
+ TRACK_SCOPE_NEAR,
+ TRACK_SCOPE_NEXT,
+ TRACK_SELECT_ALL,
+ TRACK_SELECT_ROW,
+ TRACK_SPLIT_DESCRIPTION,
+ TRACK_SHOWING_PART_OF_PATH,
+ TRACK_SHOW_BOXES_ONLY,
+ TRACK_SHOW_PATH,
+ TRACK_SINGLE_FRAME_NO_PATH,
+ TRACK_SORT_BEST,
+ TRACK_SORT_BY,
+ TRACK_SORT_CUMULATIVE,
+ TRACK_SORT_LABEL,
+ TRACK_SPLIT_HERE,
+ TRACK_SPLIT_RESULT,
+ TRACK_STAT_DISTINCT_TAXA,
+ TRACK_STAT_ID_AGREEMENT,
+ TRACK_STAT_LINKED,
+ TRACK_STAT_MOTION,
+ TRACK_STAT_SCORE_RANGE,
+ TRACK_STAT_SIZE_RATIO,
+ TRACK_SUMMARY_DERIVED_TOOLTIP,
+ TRACK_SUMMARY_DERIVED,
+ TRACK_SUMMARY_FRAMES,
+ TRACK_SUMMARY_FRAMES_WITH_VECTORS,
+ TRACK_SUMMARY_FRAME_NAMES,
+ TRACK_SUMMARY_ID_AGREEMENT,
+ TRACK_SUMMARY_SCORE_RANGE,
+ TRACK_SUMMARY_TITLE,
+ TRACK_SUMMARY_VECTORS_TOOLTIP,
+ TRACK_TIME_SPAN,
+ TRACK_UNDO_CONFIRMATION_DESCRIPTION,
+ TRACK_UNDO_CONFIRMATION_RESULT,
+ TRACK_UNDO_CONFIRMATION,
+ TRACK_WHEN_EARLIER,
+ TRACK_WHEN_LATER,
+ TRACKING_JOB_COST_THRESHOLD,
+ TRACKING_JOB_COST_THRESHOLD_DESCRIPTION,
+ TRACKING_JOB_DESCRIPTION,
+ TRACKING_JOB_NAME_CAPTURE_SET,
+ TRACKING_JOB_NAME_DESCRIPTION,
+ TRACKING_JOB_NAME_SESSION,
+ TRACKING_JOB_OPTIONS,
+ TRACKING_JOB_REQUIRE_FEATURES,
+ TRACKING_JOB_REQUIRE_FEATURES_DESCRIPTION,
+ TRACKING_JOB_RUN,
+ TRACKING_JOB_RUN_ON_SESSION,
+ TRACKING_JOB_SCOPE,
+ TRACKING_JOB_SCOPE_CAPTURE_SET,
+ TRACKING_JOB_SCOPE_SESSION,
+ TRACKING_JOB_SKIPPED_SESSIONS,
+ TRACKING_JOB_START_NOW,
+ TRACKING_JOB_TYPE,
+ TRACKING_JOB_TYPE_PROCESSING,
+ TRACKING_JOB_TYPE_TRACKING,
+ UNIDENTIFIED,
UNKNOWN_ERROR,
UNKNOWN,
UPDATING_DATA,
UPLOAD_CAPTURES,
USER_INFO,
+ VALUE_COUNT_OF_TOTAL,
VALUE_NOT_AVAILABLE,
VERIFIED_BY,
VERIFIED_OCCURRENCES,
@@ -395,6 +613,8 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.COLLAPSE]: 'Collapse',
[STRING.CONFIRM]: 'Confirm',
[STRING.CONFIRMED]: 'Confirmed',
+ [STRING.COPIED]: 'Copied',
+ [STRING.COPY_LINK]: 'Copy link',
[STRING.CREATE_NEW]: 'Create new',
[STRING.CURRENT_LOCATION]: 'Use current location',
[STRING.DELETE]: 'Delete',
@@ -409,8 +629,12 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.MORE]: 'More',
[STRING.MORE_FILTERS]: 'More filters',
[STRING.NEXT]: 'Next',
+ [STRING.NEXT_CAPTURE]: 'Next capture',
+ [STRING.NEXT_CAPTURE_WITH_DETECTIONS]: 'Next capture with detections',
[STRING.POPULATE]: 'Populate',
[STRING.PREVIOUS]: 'Previous',
+ [STRING.PREVIOUS_CAPTURE]: 'Previous capture',
+ [STRING.PREVIOUS_CAPTURE_WITH_DETECTIONS]: 'Previous capture with detections',
[STRING.PROCESS_NOW]: 'Process now',
[STRING.PROCESS]: 'Process',
[STRING.QUEUED]: 'Queued',
@@ -447,6 +671,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.FIELD_LABEL_ALGORITHM_URI]: 'Algorithm source',
[STRING.FIELD_LABEL_AVG_TEMP]: 'Avg temp',
[STRING.FIELD_LABEL_BEST_SCORE]: 'Best score',
+ [STRING.FIELD_LABEL_BOXES_WITH_VECTORS]: 'Boxes with a feature vector',
[STRING.FIELD_LABEL_CAPTURE_SET]: 'Capture set',
[STRING.FIELD_LABEL_CAPTURE]: 'Capture',
[STRING.FIELD_LABEL_CAPTURES_WITH_DETECTIONS]: 'Captures with detections',
@@ -464,6 +689,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.FIELD_LABEL_DELAY]: 'Delay',
[STRING.FIELD_LABEL_DEPLOYMENT]: 'Station',
[STRING.FIELD_LABEL_DESCRIPTION]: 'Description',
+ [STRING.FIELD_LABEL_DETECTIONS]: 'Detections',
[STRING.FIELD_LABEL_DEVICE]: 'Device type',
[STRING.FIELD_LABEL_DIRECT_OCCURRENCES]: 'Direct occurrences',
[STRING.FIELD_LABEL_DURATION]: 'Duration',
@@ -480,6 +706,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.FIELD_LABEL_GENERAL]: 'General',
[STRING.FIELD_LABEL_ICON]: 'Icon',
[STRING.FIELD_LABEL_ID]: 'ID',
+ [STRING.FIELD_LABEL_ID_AGREEMENT]: 'ID agreement',
[STRING.FIELD_LABEL_IMAGE]: 'Cover image',
[STRING.FIELD_LABEL_JOB_STATUS]: 'Job status',
[STRING.FIELD_LABEL_JOB]: 'Job',
@@ -495,9 +722,12 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.FIELD_LABEL_LOGS]: 'Logs',
[STRING.FIELD_LABEL_LONGITUDE]: 'Longitude',
[STRING.FIELD_LABEL_MOST_RECENT]: 'Most recent',
+ [STRING.FIELD_LABEL_MOTION]: 'Motion',
[STRING.FIELD_LABEL_NAME]: 'Name',
[STRING.FIELD_LABEL_NEW_PASSWORD]: 'New password',
[STRING.FIELD_LABEL_NUM_PIPELINES_REGISTERED]: 'Pipelines registered',
+ [STRING.FIELD_LABEL_DETECTION_ID]: 'Detection ID',
+ [STRING.FIELD_LABEL_OCCURRENCE_NUMBER]: 'Occurrence ID',
[STRING.FIELD_LABEL_OCCURRENCES]: 'Occurrences',
[STRING.FIELD_LABEL_PASSWORD_CURRENT]: 'Current password',
[STRING.FIELD_LABEL_PASSWORD_NEW]: 'New password',
@@ -514,9 +744,11 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.FIELD_LABEL_SAMPLING_METHOD]: 'Sampling method',
[STRING.FIELD_LABEL_SCORE]: 'Score',
[STRING.FIELD_LABEL_SESSION]: 'Session',
+ [STRING.FIELD_LABEL_SESSION_NUMBER]: 'Session ID',
[STRING.FIELD_LABEL_SESSIONS]: 'Sessions',
[STRING.FIELD_LABEL_SITE]: 'Site',
[STRING.FIELD_LABEL_SIZE]: 'Size',
+ [STRING.FIELD_LABEL_SIZE_CHANGE]: 'Size change',
[STRING.FIELD_LABEL_SLUG]: 'Slug',
[STRING.FIELD_LABEL_SNAPSHOTS]: 'Snapshots',
[STRING.FIELD_LABEL_STARTED_AT]: 'Started at',
@@ -554,6 +786,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.ENTITY_TYPE_IDENTIFICATION]: 'identification',
[STRING.ENTITY_TYPE_JOB]: 'job',
[STRING.ENTITY_TYPE_MEMBER]: 'member',
+ [STRING.ENTITY_TYPE_OCCURRENCE]: 'occurrence',
[STRING.ENTITY_TYPE_PIPELINE]: 'pipeline',
[STRING.ENTITY_TYPE_PROCESSING_SERVICE]: 'processing service',
[STRING.ENTITY_TYPE_PROJECT_DETAILS]: 'project details',
@@ -594,7 +827,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
'Are you sure you want to delete this {{type}}?',
[STRING.MESSAGE_DRAFTS]: 'Drafts are private and limited to one user.',
[STRING.MESSAGE_EXPORT_TIP]:
- 'We support two export formats: one compact and easy to use, and one that includes all raw data. To include all data in the export, skip "Capture set".',
+ 'Occurrences export as a compact CSV that is easy to use, or as JSON that includes all raw data. Projects that use tracking can also export their tracks. To include all data in the export, skip "Capture set".',
[STRING.MESSAGE_HAS_ACCOUNT]: 'Already have an account?',
[STRING.MESSAGE_IDENTIFICATION_REJECTED]:
'The update was rejected, please retry.',
@@ -788,7 +1021,7 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.SET_PASSWORD]: 'Set password',
[STRING.SETTINGS]: 'Settings',
[STRING.SHOW_DETECTIONS]: 'Show detections',
- [STRING.SNAP_TO_DETECTIONS]: 'Snap to captures with detections',
+ [STRING.SHOW_PATH_CROPS]: 'Show crops in path',
[STRING.SORT_BY]: 'Sort by',
[STRING.SORT_JOBS_ACTIVITY]: 'Jobs activity',
[STRING.SORT_OCCURRENCE_UPDATES]: 'Occurrence updates',
@@ -798,12 +1031,261 @@ const ENGLISH_STRINGS: { [key in STRING]: string } = {
[STRING.SUMMARY]: 'Summary',
[STRING.TABLE_COLUMNS]: 'Table columns',
[STRING.TERMINAL_CLASSIFICATION]: 'Terminal classification',
+ [STRING.TIMELINE_OCCURRENCE_FRAME]:
+ 'Occurrence #{{id}}: 1 capture at {{time}}',
+ [STRING.TIMELINE_OCCURRENCE_SPAN]:
+ 'Occurrence #{{id}}: {{count}} captures over {{duration}}',
+ [STRING.TIMELINE_DURATION_SECONDS]: '{{seconds}} s',
+ [STRING.TIMELINE_DURATION_MINUTES]: '{{minutes}} min',
+ [STRING.TIMELINE_DURATION_HOURS]: '{{hours}} h {{minutes}} min',
+ [STRING.TIMELINE_ACTIVITY_DESCRIPTION]:
+ 'Captures and average detections through the session. Use the capture time slider to move between captures.',
+ [STRING.TIMELINE_ACTIVITY_LABEL]: 'Session activity',
+ [STRING.TIMELINE_OCCURRENCE_ACTIVATE]: 'Go to this capture',
+ [STRING.TIMELINE_OCCURRENCE_SHOWING]:
+ 'Showing occurrence #{{id}} at {{time}}',
+ [STRING.TIMELINE_POSITION_LABEL]: 'Capture time',
+ [STRING.TIMELINE_POSITION_VALUE]: '{{time}}, capture {{index}} of {{total}}',
[STRING.TIP]: 'Tip',
+ [STRING.TRACK_BOX_EXTEND_ACTION]: '{{label}}. {{action}}',
+ [STRING.TRACK_BOX_EXTEND_LABEL]: '{{name}}, match {{match}}',
+ [STRING.TRACK_BOX_LABEL]: '{{name}}, score {{score}}',
+ [STRING.TRACK_BOX_LABEL_VERIFIED]: '{{name}}, verified',
+ [STRING.TRACK_CANDIDATE_IN_GAP]:
+ "Falls inside this track's time span, in a gap between its frames. The time is measured from the nearest track frame.",
+ [STRING.TRACK_CLEARS_CONFIRMATION]:
+ 'This clears the mark that the track is complete and accurate.',
+ [STRING.TRACK_COLUMN_COST]: 'Cost',
+ [STRING.TRACK_COLUMN_COST_HELP]:
+ "The tracking algorithm's own score for the nearest pair of frames, and the order it ranks by. Lower fits better, and a pair links when it falls under the threshold.",
+ [STRING.TRACK_COLUMN_DISTANCE]: 'Distance',
+ [STRING.TRACK_COLUMN_FRAMES]: 'Frames',
+ [STRING.TRACK_COLUMN_IMAGE]: 'Image',
+ [STRING.TRACK_COLUMN_MATCH]: 'Match',
+ [STRING.TRACK_COLUMN_MATCH_HELP]:
+ 'The same cost read on a 0 to 100 scale. A pair with no feature vector is scored on one term fewer, so its percentage is not comparable with a pair that has one. Sort by Cost for the order tracking itself uses.',
+ [STRING.TRACK_COLUMN_OVERLAP]: 'Overlap',
+ [STRING.TRACK_COLUMN_SIMILARITY]: 'Similarity',
+ [STRING.TRACK_COLUMN_SIZE]: 'Size match',
+ [STRING.TRACK_COLUMN_SPECIES]: 'Species',
+ [STRING.TRACK_COLUMN_WHEN]: 'When',
+ [STRING.TRACK_COST_OF_THRESHOLD]: '{{cost}} of {{threshold}}',
+ [STRING.TRACK_VIEW_CANDIDATE_IN_SESSION]: 'View {{name}} in session',
+ [STRING.TRACK_CONFIRM_GROUPING_DESCRIPTION]:
+ 'Records that you checked this track holds every frame of this animal, and no frames of any other. This is a separate judgement from the species it was identified as, and any later change to its frames clears it.',
+ [STRING.TRACK_CONFIRM_GROUPING_RESULT]: 'Track marked complete and accurate.',
+ [STRING.TRACK_CONFIRM_GROUPING]: 'Mark track as complete and accurate',
+ [STRING.TRACK_GROUPING_FILTER_NO]: 'Not marked',
+ [STRING.TRACK_GROUPING_FILTER_TOOLTIP]:
+ 'Whether a person has marked this track complete and accurate: it holds every frame of the animal and no frames of any other. A separate judgement from the species it was identified as.',
+ [STRING.TRACK_GROUPING_FILTER_YES]: 'Complete and accurate',
+ [STRING.TRACK_EXTEND_CAPTURE_COVERED]:
+ 'This capture already has a frame of this track',
+ [STRING.TRACK_EXTEND_CHOICE_DESCRIPTION]:
+ 'This frame is part of {{name}} with {{count}} frames. Merge the whole track into the one you are editing, or move only this frame across.',
+ [STRING.TRACK_EXTEND_CLICKED_FRAME]: 'Frame you clicked',
+ [STRING.TRACK_EXTEND_DETAILS]: 'Details',
+ [STRING.TRACK_EXTEND_HERE]: 'Edit this track',
+ [STRING.TRACK_EXTEND_HIDE_DETAILS]: 'Hide box details',
+ [STRING.TRACK_EXTEND_IN_SESSION]: 'Edit in session view',
+ [STRING.TRACK_EXTEND_LABEL]: 'Editing',
+ [STRING.TRACK_EXTEND_LATEST_FRAME]: 'Latest frame in the track',
+ [STRING.TRACK_EXTEND_MERGE_OVERLAPS]:
+ 'Both tracks have a frame on the same capture',
+ [STRING.TRACK_EXTEND_MERGE_WHOLE]: 'Merge the whole track',
+ [STRING.TRACK_EXTEND_MOVE_FRAME]: 'Move only this frame',
+ [STRING.TRACK_EXTEND_NO_LATER_CAPTURE]: 'No later capture with detections',
+ [STRING.TRACK_EXTEND_ONLY_FRAME]:
+ "The track's only frame cannot be removed or replaced",
+ [STRING.TRACK_EXTEND_REMOVE]: 'Remove',
+ [STRING.TRACK_EXTEND_REMOVE_PROMPT]: 'Remove this frame from the track?',
+ [STRING.TRACK_EXTEND_REPLACE]: 'Replace',
+ [STRING.TRACK_EXTEND_REPLACE_ORPHAN]:
+ 'The removed frame is now occurrence #{{id}}.',
+ [STRING.TRACK_EXTEND_REPLACE_PROMPT]:
+ "Replace this capture's frame with this one?",
+ [STRING.TRACK_EXTEND_SHOW_DETAILS]: 'Show box details',
+ [STRING.TRACK_EXTEND_STOP]: 'Stop editing this track',
+ [STRING.TRACK_EXTEND_TITLE]: 'Add this frame to the track',
+ [STRING.TRACK_FIRST_FRAME]: 'First track frame',
+ [STRING.TRACK_FRAMES_COUNT]: '{{count}} frames',
+ [STRING.TRACK_FRAMES_ONE]: '1 frame',
+ [STRING.TRACK_FRAMES_PAGE_EARLIER]: 'Earlier frames',
+ [STRING.TRACK_FRAMES_PAGE_ERROR]: 'These frames could not be loaded.',
+ [STRING.TRACK_FRAMES_PAGE_FIRST]: 'First frames of the track',
+ [STRING.TRACK_FRAMES_PAGE_LAST]: 'Last frames of the track',
+ [STRING.TRACK_FRAMES_PAGE_LATER]: 'Later frames',
+ [STRING.TRACK_FRAMES_RANGE]: 'Frames {{start}}–{{end}} of {{total}}',
+ [STRING.TRACK_FRAME_DETECTION]: 'Detection #{{id}}',
+ [STRING.TRACK_FRAME_IN_TRACK]: 'On this capture, already in the track',
+ [STRING.TRACK_FRAME_MENU]: 'Edit this frame',
+ [STRING.TRACK_FRAME_NO_CLASSIFICATION]: 'No classification',
+ [STRING.TRACK_FRAME_NO_CROP]: 'Crop image not available',
+ [STRING.TRACK_FRAME_NO_VECTOR]: 'No feature vector',
+ [STRING.TRACK_FRAME_THIS_CAPTURE]: 'On the capture you are viewing',
+ [STRING.TRACK_HIDE_PANEL]: 'Hide this panel',
+ [STRING.TRACK_HIDE_PATH]: 'Hide path',
+ [STRING.TRACK_JUMP_FIRST_FRAME]: 'First frame in session',
+ [STRING.TRACK_JUMP_LAST_FRAME]: 'Last frame in session',
+ [STRING.TRACK_JUMP_ONLY_FRAME]: 'Open in session',
+ [STRING.TRACK_LAST_FRAME]: 'Last track frame',
+ [STRING.TRACK_LOADING_PATH]: 'Loading path',
+ [STRING.TRACK_GAP_AFTER]: '+{{time}}',
+ [STRING.TRACK_GAP_BEFORE]: '−{{time}}',
+ [STRING.TRACK_GHOST_EARLIER]: 'Earlier frame · {{time}}',
+ [STRING.TRACK_GHOST_HINT]: 'Click to go to this frame',
+ [STRING.TRACK_GHOST_LATER]: 'Later frame · {{time}}',
+ [STRING.TRACK_GROUPING_CONFIRMED_BY]: 'by {{name}} on {{date}}',
+ [STRING.TRACK_GROUPING_CONFIRMED]: 'Complete and accurate',
+ [STRING.TRACK_GROUPING_NOT_CONFIRMED]:
+ 'Not yet marked complete and accurate by a person',
+ [STRING.TRACK_GROUPING_UNCONFIRMED]: 'Not marked complete and accurate',
+ [STRING.TRACK_MATCH_CLICK_ADD]: 'Click to add to the track',
+ [STRING.TRACK_MATCH_CLICK_CHOOSE]:
+ 'Click to choose: merge its whole track, or move just this frame',
+ [STRING.TRACK_MATCH_CLICK_REMOVE]: 'Click to remove from the track',
+ [STRING.TRACK_MATCH_CLICK_REPLACE]:
+ "Click to replace the track's frame on this capture",
+ [STRING.TRACK_MATCH_LABEL]: 'Match',
+ [STRING.TRACK_MATCH_LIKELY]: 'Likely',
+ [STRING.TRACK_MATCH_POSSIBLE]: 'Possible',
+ [STRING.TRACK_MATCH_REFERENCE_EARLIER]:
+ "Compared with the track's frame {{count}} captures earlier",
+ [STRING.TRACK_MATCH_REFERENCE_LATER]:
+ "Compared with the track's frame {{count}} captures later",
+ [STRING.TRACK_MATCH_SAME_SPECIES]:
+ '{{count}} more of this species on this capture',
+ [STRING.TRACK_MATCH_SCORE]: '{{level}} ({{percent}}%)',
+ [STRING.TRACK_MATCH_SKIPPED_NO_VECTOR]:
+ 'Tracking would skip this: no feature vector',
+ [STRING.TRACK_MATCH_SKIPPED_SESSION]:
+ 'Automatic tracking would skip this session: no feature vectors. Colors use box geometry only.',
+ [STRING.TRACK_MATCH_UNLIKELY]: 'Unlikely',
+ [STRING.TRACK_MATCH_WOULD_LINK]: 'Automatic tracking would link this',
+ [STRING.TRACK_MERGE_CANDIDATES_LOAD_FAILED]:
+ 'Could not load the candidates. Close this dialog and try again.',
+ [STRING.TRACK_MERGE_CANDIDATES_TITLE]: 'Candidates to merge',
+ [STRING.TRACK_MERGE_COUNT]: 'Merge {{count}} occurrences',
+ [STRING.TRACK_MERGE_MANY_DESCRIPTION]:
+ 'The detections and identifications of the occurrences you tick move here, and those occurrences are deleted. Only occurrences from this session can be merged.',
+ [STRING.TRACK_MERGE_ONE]: 'Merge 1 occurrence',
+ [STRING.TRACK_MERGE_SCOPE_DESCRIPTION]:
+ 'Searching {{scope}}. Lists occurrences seen just before or after this one, or in a gap in its track, best match by the tracking method first. Distance and similarity compare the two frames nearest in time.',
+ [STRING.TRACK_MERGE_RESULT]:
+ 'Merged. This occurrence now holds {{count}} frames.',
+ [STRING.TRACK_MERGE]: 'Merge with another occurrence',
+ [STRING.TRACK_MOVE_DESCRIPTION]:
+ 'The frame at {{time}} moves to the occurrence you pick. The rest of this occurrence stays together.',
+ [STRING.TRACK_MOVE_FRAME]: 'Move to another occurrence',
+ [STRING.TRACK_MOVE_SCOPE_DESCRIPTION]:
+ 'Searching {{scope}}. Lists occurrences this frame could belong to, best match to it by the tracking method first. Occurrences with a box on the same capture are left out.',
+ [STRING.TRACK_MOVE_RESULT]:
+ 'Moved. Occurrence #{{id}} now holds {{count}} frames.',
+ [STRING.TRACK_NEXT_FRAME]: 'Next track frame',
+ [STRING.TRACK_NO_MERGE_CANDIDATES_SCOPE]:
+ 'No other occurrence of this session was found in this scope. Try a wider scope.',
+ [STRING.TRACK_OFFSET_HOURS]: '{{hours}} h {{minutes}} min',
+ [STRING.TRACK_OFFSET_MINUTES]: '{{count}} min',
+ [STRING.TRACK_OFFSET_SECONDS]: '{{count}} s',
+ [STRING.TRACK_OPEN_NEW_OCCURRENCE]: 'Open it',
+ [STRING.TRACK_PATH_ERROR]: 'Could not load the path',
+ [STRING.TRACK_PATH_RANGE]: '{{start}}–{{end}} · {{frames}}',
+ [STRING.TRACK_PATH_STATUS_ERROR]:
+ 'Could not load the path for occurrence #{{id}}',
+ [STRING.TRACK_PATH_STATUS_LOADING]: 'Loading path for occurrence #{{id}}',
+ [STRING.TRACK_PATH_STATUS_SHOWN]: 'Path shown for occurrence #{{id}}',
+ [STRING.TRACK_PATH_STATUS_SHOWN_FOR]: 'Path shown for',
+ [STRING.TRACK_PATH_OCCURRENCE]: 'occurrence #{{id}}',
+ [STRING.TRACK_PATH_GO_TO_FRAME]: 'Go to frame at {{time}}',
+ [STRING.TRACK_PATH_GO_TO_NEAREST]:
+ 'Go to the nearest capture with this occurrence',
+ [STRING.TRACK_PERCENT]: '{{percent}}%',
+ [STRING.TRACK_PICK_OCCURRENCE]: 'Candidates near this frame',
+ [STRING.TRACK_POSITION_AFTER_LAST]: 'After the last frame',
+ [STRING.TRACK_POSITION_BEFORE_FIRST]: 'Before the first frame',
+ [STRING.TRACK_POSITION_BETWEEN]: 'Between frames {{before}} and {{after}}',
+ [STRING.TRACK_POSITION_FRAME]: 'Frame {{index}} of {{total}}',
+ [STRING.TRACK_PREVIOUS_FRAME]: 'Previous track frame',
+ [STRING.TRACK_REMOVE_DESCRIPTION]:
+ 'The frame at {{time}} becomes an occurrence of its own, leaving {{remaining}} of {{total}} frames here. The rest of the track is stitched back together.',
+ [STRING.TRACK_REMOVE_FRAME]: 'Remove this frame',
+ [STRING.TRACK_REMOVE_RESULT]:
+ 'Removed. The frame is now occurrence #{{id}} on its own.',
+ [STRING.TRACK_SCOPE_LABEL]: 'Search scope',
+ [STRING.TRACK_SCOPE_MINUTES_30]: '±30 minutes',
+ [STRING.TRACK_SCOPE_MINUTES_5]: '±5 minutes',
+ [STRING.TRACK_SCOPE_NEAR]: '3 captures either side',
+ [STRING.TRACK_SCOPE_NEXT]: 'Adjacent captures',
+ [STRING.TRACK_SELECT_ALL]: 'Select all shown',
+ [STRING.TRACK_SELECT_ROW]: 'Select {{name}}',
+ [STRING.TRACK_SPLIT_DESCRIPTION]:
+ 'Everything from {{time}} onwards — {{moved}} of {{total}} frames — becomes a new occurrence. The earlier frames stay here.',
+ [STRING.TRACK_SHOWING_PART_OF_PATH]: 'Showing {{shown}} of {{total}} frames',
+ [STRING.TRACK_SHOW_BOXES_ONLY]: 'Show boxes only',
+ [STRING.TRACK_SHOW_PATH]: 'Show path',
+ [STRING.TRACK_SINGLE_FRAME_NO_PATH]: 'No path — one frame only',
+ [STRING.TRACK_SORT_BEST]: 'Best match',
+ [STRING.TRACK_SORT_BY]: 'Sort by {{column}}',
+ [STRING.TRACK_SORT_CUMULATIVE]: 'Time, then distance, then similarity',
+ [STRING.TRACK_SORT_LABEL]: 'Order candidates',
+ [STRING.TRACK_SPLIT_HERE]: 'Split here',
+ [STRING.TRACK_SPLIT_RESULT]:
+ 'Split. {{count}} frames moved to occurrence #{{id}}.',
+ [STRING.TRACK_STAT_DISTINCT_TAXA]: '{{count}} taxa',
+ [STRING.TRACK_STAT_ID_AGREEMENT]: '{{percent}}%',
+ [STRING.TRACK_STAT_LINKED]: '{{count}} linked',
+ [STRING.TRACK_STAT_MOTION]: '{{percent}}% of frame',
+ [STRING.TRACK_STAT_SCORE_RANGE]: '{{min}}–{{max}} · mean {{mean}}',
+ [STRING.TRACK_STAT_SIZE_RATIO]: '×{{ratio}}',
+ [STRING.TRACK_SUMMARY_DERIVED_TOOLTIP]:
+ 'Computed from the current detections when this page loads; not saved and not included in exports',
+ [STRING.TRACK_SUMMARY_DERIVED]: 'Derived, not stored',
+ [STRING.TRACK_SUMMARY_FRAMES]: 'Frames grouped',
+ [STRING.TRACK_SUMMARY_FRAMES_WITH_VECTORS]: 'Frames with a feature vector',
+ [STRING.TRACK_SUMMARY_FRAME_NAMES]: 'Names given to frames',
+ [STRING.TRACK_SUMMARY_ID_AGREEMENT]: 'Identification agreement',
+ [STRING.TRACK_SUMMARY_SCORE_RANGE]: 'Score range',
+ [STRING.TRACK_SUMMARY_TITLE]: 'Occurrence tracking',
+ [STRING.TRACK_SUMMARY_VECTORS_TOOLTIP]:
+ 'Tracking and the merge picker compare frames by these feature vectors.',
+ [STRING.TRACK_TIME_SPAN]: '{{start}}–{{end}}',
+ [STRING.TRACK_UNDO_CONFIRMATION_DESCRIPTION]:
+ 'Withdraws the mark that this track is complete and accurate. The frames themselves are left untouched.',
+ [STRING.TRACK_UNDO_CONFIRMATION_RESULT]: 'Mark withdrawn.',
+ [STRING.TRACK_UNDO_CONFIRMATION]: 'Undo this mark',
+ [STRING.UNIDENTIFIED]: 'Unidentified',
[STRING.UNKNOWN_ERROR]: 'Unknown error',
[STRING.UNKNOWN]: 'Unknown',
[STRING.UPDATING_DATA]: 'Updating data',
+ [STRING.TRACK_WHEN_EARLIER]: '{{time}} earlier',
+ [STRING.TRACK_WHEN_LATER]: '{{time}} later',
+ [STRING.TRACKING_JOB_COST_THRESHOLD]: 'Cost threshold',
+ [STRING.TRACKING_JOB_COST_THRESHOLD_DESCRIPTION]:
+ 'The highest matching cost at which two detections are still linked. Lower links fewer, closer matches. Leave empty for the default.',
+ [STRING.TRACKING_JOB_DESCRIPTION]:
+ 'Tracking links detections of the same individual in consecutive captures into one occurrence.',
+ [STRING.TRACKING_JOB_NAME_CAPTURE_SET]: 'Tracking capture set {{id}}',
+ [STRING.TRACKING_JOB_NAME_DESCRIPTION]:
+ 'Left empty, the job is named after what it tracks.',
+ [STRING.TRACKING_JOB_NAME_SESSION]: 'Tracking session {{id}}',
+ [STRING.TRACKING_JOB_OPTIONS]: 'Options',
+ [STRING.TRACKING_JOB_REQUIRE_FEATURES]: 'Require feature vectors',
+ [STRING.TRACKING_JOB_REQUIRE_FEATURES_DESCRIPTION]:
+ 'Only link detections that have a feature vector. Turn this off for captures processed before vectors were stored: matching then relies on position and size alone and links more loosely.',
+ [STRING.TRACKING_JOB_RUN]: 'Run tracking',
+ [STRING.TRACKING_JOB_RUN_ON_SESSION]: 'Run tracking on this session',
+ [STRING.TRACKING_JOB_SCOPE]: 'Track',
+ [STRING.TRACKING_JOB_SCOPE_CAPTURE_SET]: 'A capture set',
+ [STRING.TRACKING_JOB_SCOPE_SESSION]: 'One session',
+ [STRING.TRACKING_JOB_SKIPPED_SESSIONS]:
+ 'Tracking skips a session if it has human identifications, or if any of its occurrences already groups several detections (from an earlier tracking run or a manual merge). It can also skip sessions that are not fully processed or lack feature vectors.',
+ [STRING.TRACKING_JOB_START_NOW]: 'Start immediately',
+ [STRING.TRACKING_JOB_TYPE]: 'Job type',
+ [STRING.TRACKING_JOB_TYPE_PROCESSING]: 'Process images',
+ [STRING.TRACKING_JOB_TYPE_TRACKING]: 'Track occurrences',
[STRING.UPLOAD_CAPTURES]: 'Upload captures',
[STRING.USER_INFO]: 'User info',
+ [STRING.VALUE_COUNT_OF_TOTAL]: '{{count}} of {{total}}',
[STRING.VALUE_NOT_AVAILABLE]: 'n/a',
[STRING.VERIFIED_BY]: 'Verified by\n{{name}}',
[STRING.VERIFIED_OCCURRENCES]: 'Verified occurrences',
diff --git a/ui/src/utils/parseServerError/parseServerError.test.ts b/ui/src/utils/parseServerError/parseServerError.test.ts
index 8741764d6..31c860cbb 100644
--- a/ui/src/utils/parseServerError/parseServerError.test.ts
+++ b/ui/src/utils/parseServerError/parseServerError.test.ts
@@ -21,6 +21,24 @@ describe('parseServerError', () => {
})
})
+ test('keeps the whole message when a field error is a bare string', () => {
+ const EXAMPLE_ERROR = {
+ message: 'Request failed with status code 400',
+ response: {
+ data: {
+ occurrence_ids: 'Capture(s) [12] would hold two detections.',
+ },
+ },
+ }
+
+ expect(parseServerError(EXAMPLE_ERROR).fieldErrors).toEqual([
+ {
+ key: 'occurrence_ids',
+ message: 'Capture(s) [12] would hold two detections.',
+ },
+ ])
+ })
+
test('returns max one message per field', () => {
const EXAMPLE_ERROR = {
response: {
diff --git a/ui/src/utils/parseServerError/parseServerError.ts b/ui/src/utils/parseServerError/parseServerError.ts
index a149038de..ec0f2d474 100644
--- a/ui/src/utils/parseServerError/parseServerError.ts
+++ b/ui/src/utils/parseServerError/parseServerError.ts
@@ -18,7 +18,12 @@ export const parseServerError = (error: any) => {
if (key === 'non_field_errors' || key === 'detail') {
message = details as string
} else {
- fieldErrors.push({ key, message: `${(details as string[])[0]}` })
+ // DRF sends a field error as a list of strings, or as a bare string when a
+ // view raises ValidationError({field: "message"}); take the whole message either way.
+ fieldErrors.push({
+ key,
+ message: `${Array.isArray(details) ? details[0] : details}`,
+ })
}
}
})
diff --git a/ui/src/utils/project-features/project-features.test.ts b/ui/src/utils/project-features/project-features.test.ts
new file mode 100644
index 000000000..81d0d09b8
--- /dev/null
+++ b/ui/src/utils/project-features/project-features.test.ts
@@ -0,0 +1,63 @@
+import {
+ canStartTracking,
+ hasProjectFeature,
+ withoutTrackingColumns,
+} from './project-features'
+import { UserPermission } from 'utils/user/types'
+
+describe('hasProjectFeature', () => {
+ test('is on only when the flag is true', () => {
+ expect(hasProjectFeature({ tracking: true }, 'tracking')).toBe(true)
+ expect(hasProjectFeature({ tracking: false }, 'tracking')).toBe(false)
+ expect(hasProjectFeature({ tags: true }, 'tracking')).toBe(false)
+ })
+
+ test('is off while the project has not loaded', () => {
+ expect(hasProjectFeature(undefined, 'tracking')).toBe(false)
+ expect(hasProjectFeature({}, 'tracking')).toBe(false)
+ })
+})
+
+describe('withoutTrackingColumns', () => {
+ const columns = [
+ { id: 'id' },
+ { id: 'detections' },
+ { id: 'duration' },
+ { id: 'motion' },
+ { id: 'size-change' },
+ { id: 'id-agreement' },
+ ]
+
+ test('drops the track statistic columns when tracking is off', () => {
+ expect(withoutTrackingColumns(columns, false).map((c) => c.id)).toEqual([
+ 'id',
+ 'duration',
+ ])
+ })
+
+ test('keeps every column when tracking is on', () => {
+ expect(withoutTrackingColumns(columns, true)).toEqual(columns)
+ })
+})
+
+describe('canStartTracking', () => {
+ const run = [UserPermission.RunTracking]
+
+ test('needs both the flag and the run tracking permission', () => {
+ const flags = { tracking: true }
+ expect(
+ canStartTracking({ userPermissions: run, featureFlags: flags })
+ ).toBe(true)
+ expect(
+ canStartTracking({
+ userPermissions: [UserPermission.Update],
+ featureFlags: flags,
+ })
+ ).toBe(false)
+ expect(canStartTracking({ featureFlags: flags })).toBe(false)
+ expect(canStartTracking({ userPermissions: run, featureFlags: {} })).toBe(
+ false
+ )
+ expect(canStartTracking(undefined)).toBe(false)
+ })
+})
diff --git a/ui/src/utils/project-features/project-features.ts b/ui/src/utils/project-features/project-features.ts
new file mode 100644
index 000000000..510b45ded
--- /dev/null
+++ b/ui/src/utils/project-features/project-features.ts
@@ -0,0 +1,37 @@
+import { UserPermission } from 'utils/user/types'
+
+export type ProjectFeature = 'tags' | 'tracking'
+
+/** Flags a project owner has not switched on read as off, including while loading. */
+export const hasProjectFeature = (
+ featureFlags: { [key: string]: boolean } | undefined,
+ feature: ProjectFeature
+) => featureFlags?.[feature] === true
+
+/** The server grants run_tracking only while tracking is on, so the flag check is a safeguard. */
+export const canStartTracking = (
+ project:
+ | {
+ featureFlags?: { [key: string]: boolean }
+ userPermissions?: UserPermission[]
+ }
+ | undefined
+) =>
+ !!project?.userPermissions?.includes(UserPermission.RunTracking) &&
+ hasProjectFeature(project.featureFlags, 'tracking')
+
+// Occurrence list columns that only mean something once occurrences are tracks.
+export const TRACKING_COLUMN_IDS = [
+ 'detections',
+ 'motion',
+ 'size-change',
+ 'id-agreement',
+]
+
+export const withoutTrackingColumns = (
+ columns: T[],
+ trackingEnabled: boolean
+) =>
+ trackingEnabled
+ ? columns
+ : columns.filter((column) => !TRACKING_COLUMN_IDS.includes(column.id))
diff --git a/ui/src/utils/project-features/useProjectFeature.ts b/ui/src/utils/project-features/useProjectFeature.ts
new file mode 100644
index 000000000..8954e4719
--- /dev/null
+++ b/ui/src/utils/project-features/useProjectFeature.ts
@@ -0,0 +1,21 @@
+import { useProjectDetails } from 'data-services/hooks/projects/useProjectDetails'
+import { useParams } from 'react-router-dom'
+import {
+ canStartTracking,
+ hasProjectFeature,
+ ProjectFeature,
+} from './project-features'
+
+export const useProjectFeature = (feature: ProjectFeature) => {
+ const { projectId } = useParams()
+ const { project } = useProjectDetails(projectId as string, true)
+
+ return hasProjectFeature(project?.featureFlags, feature)
+}
+
+export const useCanStartTracking = () => {
+ const { projectId } = useParams()
+ const { project } = useProjectDetails(projectId as string, true)
+
+ return canStartTracking(project)
+}
diff --git a/ui/src/utils/useFilters.ts b/ui/src/utils/useFilters.ts
index 8e9dd3336..c9779997b 100644
--- a/ui/src/utils/useFilters.ts
+++ b/ui/src/utils/useFilters.ts
@@ -146,6 +146,13 @@ export const AVAILABLE_FILTERS = (projectId: string): FilterConfig[] => [
},
},
},
+ {
+ label: translate(STRING.TRACK_GROUPING_CONFIRMED),
+ field: 'grouping_verified',
+ tooltip: {
+ text: translate(STRING.TRACK_GROUPING_FILTER_TOOLTIP),
+ },
+ },
{
label: 'Processing status',
field: 'processed',
diff --git a/ui/src/utils/user/types.ts b/ui/src/utils/user/types.ts
index 66cc5a53b..10392d23a 100644
--- a/ui/src/utils/user/types.ts
+++ b/ui/src/utils/user/types.ts
@@ -16,6 +16,7 @@ export enum UserPermission {
Populate = 'populate', // Custom collection permission
Run = 'run', // Custom job permission
RunSingleImage = 'run_single_image_ml_job', // Custom job permission
+ RunTracking = 'run_tracking', // Custom project permission
Star = 'star',
Sync = 'sync', // Custom deployment permission (sync_deployment)
Update = 'update',
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 188e5ec68..c705cf33b 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -1825,26 +1825,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-arrow@npm:1.0.3":
- version: 1.0.3
- resolution: "@radix-ui/react-arrow@npm:1.0.3"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/react-primitive": "npm:1.0.3"
- peerDependencies:
- "@types/react": "*"
- "@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- "@types/react-dom":
- optional: true
- checksum: c931f6d7e0bac50fd1654a0303a303aff74a68a13a33a851a43a7c88677b53a92ca6557920b9105144a3002f899ce888437d20ddd7803a5c716edac99587626d
- languageName: node
- linkType: hard
-
"@radix-ui/react-arrow@npm:1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-arrow@npm:1.1.7"
@@ -2225,30 +2205,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-dismissable-layer@npm:1.0.4":
- version: 1.0.4
- resolution: "@radix-ui/react-dismissable-layer@npm:1.0.4"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/primitive": "npm:1.0.1"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-use-callback-ref": "npm:1.0.1"
- "@radix-ui/react-use-escape-keydown": "npm:1.0.3"
- peerDependencies:
- "@types/react": "*"
- "@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- "@types/react-dom":
- optional: true
- checksum: a7b9695092cd4109a7b4a4a66b7f634c42d4f39aa0893621a8ee5e8bc90f8ae27e741df66db726c341a60d2115e3f813520fee1f5cc4fb05d77914b4ade3819f
- languageName: node
- linkType: hard
-
"@radix-ui/react-dismissable-layer@npm:1.1.11":
version: 1.1.11
resolution: "@radix-ui/react-dismissable-layer@npm:1.1.11"
@@ -2272,29 +2228,28 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-dropdown-menu@npm:^2.0.5":
- version: 2.0.5
- resolution: "@radix-ui/react-dropdown-menu@npm:2.0.5"
+"@radix-ui/react-dropdown-menu@npm:2.1.16":
+ version: 2.1.16
+ resolution: "@radix-ui/react-dropdown-menu@npm:2.1.16"
dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/primitive": "npm:1.0.1"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-context": "npm:1.0.1"
- "@radix-ui/react-id": "npm:1.0.1"
- "@radix-ui/react-menu": "npm:2.0.5"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-use-controllable-state": "npm:1.0.1"
+ "@radix-ui/primitive": "npm:1.1.3"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-menu": "npm:2.1.16"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
- checksum: fdc163b07894101a88bd2b0a6a425838b5e4164fb0dcf306e215120ebe034c7f2c114f7d4c2b20eb48d90402fb1caefb61a22b2c2084b40d63fa551429fe6e86
+ checksum: 8caaa8dd791ccb284568720adafa59855e13860aa29eb20e10a04ba671cbbfa519a4c5d3a339a4d9fb08009eeb1065f4a8b5c3c8ef45e9753161cc560106b935
languageName: node
linkType: hard
@@ -2309,21 +2264,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-focus-guards@npm:1.0.1":
- version: 1.0.1
- resolution: "@radix-ui/react-focus-guards@npm:1.0.1"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- peerDependencies:
- "@types/react": "*"
- react: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- checksum: d5fd4e5aa9d9a87c8ad490b3b4992d6f1d9eddf18e56df2a2bcf8744c4332b275d73377fd193df3e6ba0ad9608dc497709beca5c64de2b834d5f5350b3c9a272
- languageName: node
- linkType: hard
-
"@radix-ui/react-focus-guards@npm:1.1.3":
version: 1.1.3
resolution: "@radix-ui/react-focus-guards@npm:1.1.3"
@@ -2352,28 +2292,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-focus-scope@npm:1.0.3":
- version: 1.0.3
- resolution: "@radix-ui/react-focus-scope@npm:1.0.3"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-use-callback-ref": "npm:1.0.1"
- peerDependencies:
- "@types/react": "*"
- "@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- "@types/react-dom":
- optional: true
- checksum: bfff46919666c122f5b812ee427494ae8408c0eebee30337bd2ce0eedf539f0feaa242f790304ef9df15425b837010ffc6061ce467bedd2c5fd9373bee2b95da
- languageName: node
- linkType: hard
-
"@radix-ui/react-focus-scope@npm:1.1.7":
version: 1.1.7
resolution: "@radix-ui/react-focus-scope@npm:1.1.7"
@@ -2453,103 +2371,72 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-menu@npm:2.0.5":
- version: 2.0.5
- resolution: "@radix-ui/react-menu@npm:2.0.5"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/primitive": "npm:1.0.1"
- "@radix-ui/react-collection": "npm:1.0.3"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-context": "npm:1.0.1"
- "@radix-ui/react-direction": "npm:1.0.1"
- "@radix-ui/react-dismissable-layer": "npm:1.0.4"
- "@radix-ui/react-focus-guards": "npm:1.0.1"
- "@radix-ui/react-focus-scope": "npm:1.0.3"
- "@radix-ui/react-id": "npm:1.0.1"
- "@radix-ui/react-popper": "npm:1.1.2"
- "@radix-ui/react-portal": "npm:1.0.3"
- "@radix-ui/react-presence": "npm:1.0.1"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-roving-focus": "npm:1.0.4"
- "@radix-ui/react-slot": "npm:1.0.2"
- "@radix-ui/react-use-callback-ref": "npm:1.0.1"
- aria-hidden: "npm:^1.1.1"
- react-remove-scroll: "npm:2.5.5"
- peerDependencies:
- "@types/react": "*"
- "@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- "@types/react-dom":
- optional: true
- checksum: 9b21a5fb159dc106ea7b45c64cf08487fb4cc1bba2e8696d70320f3e4173f2a0098eeeef16bc846391f569fd2abb5fe7b2ef40de929b9625e18a27f702743705
- languageName: node
- linkType: hard
-
-"@radix-ui/react-popover@npm:^1.0.6":
- version: 1.0.6
- resolution: "@radix-ui/react-popover@npm:1.0.6"
+"@radix-ui/react-menu@npm:2.1.16":
+ version: 2.1.16
+ resolution: "@radix-ui/react-menu@npm:2.1.16"
dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/primitive": "npm:1.0.1"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-context": "npm:1.0.1"
- "@radix-ui/react-dismissable-layer": "npm:1.0.4"
- "@radix-ui/react-focus-guards": "npm:1.0.1"
- "@radix-ui/react-focus-scope": "npm:1.0.3"
- "@radix-ui/react-id": "npm:1.0.1"
- "@radix-ui/react-popper": "npm:1.1.2"
- "@radix-ui/react-portal": "npm:1.0.3"
- "@radix-ui/react-presence": "npm:1.0.1"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-slot": "npm:1.0.2"
- "@radix-ui/react-use-controllable-state": "npm:1.0.1"
- aria-hidden: "npm:^1.1.1"
- react-remove-scroll: "npm:2.5.5"
+ "@radix-ui/primitive": "npm:1.1.3"
+ "@radix-ui/react-collection": "npm:1.1.7"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-direction": "npm:1.1.1"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.11"
+ "@radix-ui/react-focus-guards": "npm:1.1.3"
+ "@radix-ui/react-focus-scope": "npm:1.1.7"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-popper": "npm:1.2.8"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.5"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-roving-focus": "npm:1.1.11"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-callback-ref": "npm:1.1.1"
+ aria-hidden: "npm:^1.2.4"
+ react-remove-scroll: "npm:^2.6.3"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
- checksum: 1b9fd21f6b73a74af88c5cd151f7de2a8dcc46a4d7ac7bee1104d5a45869f170bcb2490c3205b18597457b9153e71016bc1f71077938317e07eca5cf45fca7ae
+ checksum: 27516b2b987fa9181c4da8645000af8f60691866a349d7a46b9505fa7d2e9d92b9e364db4f7305d08e9e57d0e1afc8df8354f8ee3c12aa05c0100c16b0e76c27
languageName: node
linkType: hard
-"@radix-ui/react-popper@npm:1.1.2":
- version: 1.1.2
- resolution: "@radix-ui/react-popper@npm:1.1.2"
+"@radix-ui/react-popover@npm:1.1.15":
+ version: 1.1.15
+ resolution: "@radix-ui/react-popover@npm:1.1.15"
dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@floating-ui/react-dom": "npm:^2.0.0"
- "@radix-ui/react-arrow": "npm:1.0.3"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-context": "npm:1.0.1"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-use-callback-ref": "npm:1.0.1"
- "@radix-ui/react-use-layout-effect": "npm:1.0.1"
- "@radix-ui/react-use-rect": "npm:1.0.1"
- "@radix-ui/react-use-size": "npm:1.0.1"
- "@radix-ui/rect": "npm:1.0.1"
+ "@radix-ui/primitive": "npm:1.1.3"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.11"
+ "@radix-ui/react-focus-guards": "npm:1.1.3"
+ "@radix-ui/react-focus-scope": "npm:1.1.7"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-popper": "npm:1.2.8"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.5"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ aria-hidden: "npm:^1.2.4"
+ react-remove-scroll: "npm:^2.6.3"
peerDependencies:
"@types/react": "*"
"@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
- checksum: 4bd069b79f7046af2c0967b8e43f727cd09834cbd6df1e3d5a943c4f83428ff8b646882737fdf7593c22e261a1d13768a5c020138d79503862ae2e1729081bba
+ checksum: c1c76b5e5985b128d03b621424fb453f769931d497759a1977734d303007da9f970570cf3ea1f6968ab609ab4a97f384168bff056197bd2d3d422abea0e3614b
languageName: node
linkType: hard
@@ -2594,26 +2481,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-portal@npm:1.0.3":
- version: 1.0.3
- resolution: "@radix-ui/react-portal@npm:1.0.3"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/react-primitive": "npm:1.0.3"
- peerDependencies:
- "@types/react": "*"
- "@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- "@types/react-dom":
- optional: true
- checksum: baf295bbbf09ead37b64ee1dc025a6a540960f5e60552766d78f6065504c67d4bcf49fad5e2073617d9a3011daafad625aa3bd1da7a886c704833b22a49e888f
- languageName: node
- linkType: hard
-
"@radix-ui/react-portal@npm:1.1.9":
version: 1.1.9
resolution: "@radix-ui/react-portal@npm:1.1.9"
@@ -2814,34 +2681,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-roving-focus@npm:1.0.4":
- version: 1.0.4
- resolution: "@radix-ui/react-roving-focus@npm:1.0.4"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/primitive": "npm:1.0.1"
- "@radix-ui/react-collection": "npm:1.0.3"
- "@radix-ui/react-compose-refs": "npm:1.0.1"
- "@radix-ui/react-context": "npm:1.0.1"
- "@radix-ui/react-direction": "npm:1.0.1"
- "@radix-ui/react-id": "npm:1.0.1"
- "@radix-ui/react-primitive": "npm:1.0.3"
- "@radix-ui/react-use-callback-ref": "npm:1.0.1"
- "@radix-ui/react-use-controllable-state": "npm:1.0.1"
- peerDependencies:
- "@types/react": "*"
- "@types/react-dom": "*"
- react: ^16.8 || ^17.0 || ^18.0
- react-dom: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- "@types/react-dom":
- optional: true
- checksum: 61e3ddfd1647e64fba855434ff41e8e7ba707244fe8841f78c450fbdce525383b64259279475615d030dbf1625cbffd8eeebee72d91bf6978794f5dbcf887fc0
- languageName: node
- linkType: hard
-
"@radix-ui/react-roving-focus@npm:1.1.0":
version: 1.1.0
resolution: "@radix-ui/react-roving-focus@npm:1.1.0"
@@ -2869,6 +2708,33 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-roving-focus@npm:1.1.11":
+ version: 1.1.11
+ resolution: "@radix-ui/react-roving-focus@npm:1.1.11"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.3"
+ "@radix-ui/react-collection": "npm:1.1.7"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-direction": "npm:1.1.1"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-use-callback-ref": "npm:1.1.1"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 2cd43339c36e89a3bf1db8aab34b939113dfbde56bf3a33df2d74757c78c9489b847b1962f1e2441c67e41817d120cb6177943e0f655f47bc1ff8e44fd55b1a2
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-select@npm:^2.2.6":
version: 2.2.6
resolution: "@radix-ui/react-select@npm:2.2.6"
@@ -3260,22 +3126,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-use-escape-keydown@npm:1.0.3":
- version: 1.0.3
- resolution: "@radix-ui/react-use-escape-keydown@npm:1.0.3"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/react-use-callback-ref": "npm:1.0.1"
- peerDependencies:
- "@types/react": "*"
- react: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- checksum: 3c94c78902dcb40b60083ee2184614f45c95a189178f52d89323b467bd04bcf5fdb1bc4d43debecd7f0b572c3843c7e04edbcb56f40a4b4b43936fb2770fb8ad
- languageName: node
- linkType: hard
-
"@radix-ui/react-use-escape-keydown@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1"
@@ -3367,22 +3217,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-use-rect@npm:1.0.1":
- version: 1.0.1
- resolution: "@radix-ui/react-use-rect@npm:1.0.1"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/rect": "npm:1.0.1"
- peerDependencies:
- "@types/react": "*"
- react: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- checksum: 94c5ab31dfd3678c0cb77a30025e82b3a287577c1a8674b0d703a36d27434bc9c59790e0bebf57ed153f0b8e0d8c3b9675fc9787b9eac525a09abcda8fa9e7eb
- languageName: node
- linkType: hard
-
"@radix-ui/react-use-rect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-rect@npm:1.1.1"
@@ -3410,22 +3244,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-use-size@npm:1.0.1":
- version: 1.0.1
- resolution: "@radix-ui/react-use-size@npm:1.0.1"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- "@radix-ui/react-use-layout-effect": "npm:1.0.1"
- peerDependencies:
- "@types/react": "*"
- react: ^16.8 || ^17.0 || ^18.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- checksum: b109a4b3781781c4dc641a1173f0a6fcb0b0f7b2d7cdba5848a46070c9fb4e518909a46c20a3c2efbc78737c64859c59ead837f2940e8c8394d1c503ef58773b
- languageName: node
- linkType: hard
-
"@radix-ui/react-use-size@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/react-use-size@npm:1.1.1"
@@ -3460,15 +3278,6 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/rect@npm:1.0.1":
- version: 1.0.1
- resolution: "@radix-ui/rect@npm:1.0.1"
- dependencies:
- "@babel/runtime": "npm:^7.13.10"
- checksum: 4c5159661340acc31b11e1f2ebd87a1521d39bfa287544dd2cd75b399539a4b625d38a1501c90ceae21fcca18ed164b0c3735817ff140ae334098192c110e571
- languageName: node
- linkType: hard
-
"@radix-ui/rect@npm:1.1.1":
version: 1.1.1
resolution: "@radix-ui/rect@npm:1.1.1"
@@ -4704,8 +4513,8 @@ __metadata:
"@radix-ui/react-accordion": "npm:^1.1.2"
"@radix-ui/react-checkbox": "npm:^1.0.3"
"@radix-ui/react-dialog": "npm:^1.1.15"
- "@radix-ui/react-dropdown-menu": "npm:^2.0.5"
- "@radix-ui/react-popover": "npm:^1.0.6"
+ "@radix-ui/react-dropdown-menu": "npm:2.1.16"
+ "@radix-ui/react-popover": "npm:1.1.15"
"@radix-ui/react-portal": "npm:^1.1.1"
"@radix-ui/react-select": "npm:^2.2.6"
"@radix-ui/react-slider": "npm:^1.1.1"
@@ -11302,25 +11111,6 @@ __metadata:
languageName: node
linkType: hard
-"react-remove-scroll@npm:2.5.5":
- version: 2.5.5
- resolution: "react-remove-scroll@npm:2.5.5"
- dependencies:
- react-remove-scroll-bar: "npm:^2.3.3"
- react-style-singleton: "npm:^2.2.1"
- tslib: "npm:^2.1.0"
- use-callback-ref: "npm:^1.3.0"
- use-sidecar: "npm:^1.1.2"
- peerDependencies:
- "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
- react: ^16.8.0 || ^17.0.0 || ^18.0.0
- peerDependenciesMeta:
- "@types/react":
- optional: true
- checksum: 4952657e6a7b9d661d4ad4dfcef81b9c7fa493e35164abff99c35c0b27b3d172ef7ad70c09416dc44dd14ff2e6b38a5ec7da27e27e90a15cbad36b8fd2fd8054
- languageName: node
- linkType: hard
-
"react-remove-scroll@npm:^2.6.3":
version: 2.7.2
resolution: "react-remove-scroll@npm:2.7.2"