diff --git a/INSTRUCTIONS.md b/INSTRUCTIONS.md index 74928bc..9a78947 100644 --- a/INSTRUCTIONS.md +++ b/INSTRUCTIONS.md @@ -67,8 +67,8 @@ that area in the main view. On the right side of the screen is a list of the Sequence and Pulse annotations. These are tabbed views that can be switched between by clicking on the Tab Name. Annotations can be selected by clicking on them. -When an annotation is selected it can be edited including the species -comments and other information. +When an annotation is selected it can be edited including the +species comments and other information. **Sequence** - A Sequence annotation is typically used to group multiple pulses together This can be drawn by clicking on the 'Add+' button and drawing a box around the pulses. diff --git a/bats_ai/core/views/recording.py b/bats_ai/core/views/recording.py index 239c672..2dda201 100644 --- a/bats_ai/core/views/recording.py +++ b/bats_ai/core/views/recording.py @@ -10,7 +10,8 @@ from ninja.files import UploadedFile from ninja.pagination import RouterPaginated -from bats_ai.core.models import Annotations, Recording, Species, Spectrogram, TemporalAnnotations +from bats_ai.core.models import Annotations, Recording, Species, TemporalAnnotations +from bats_ai.core.tasks import recording_compute_spectrogram from bats_ai.core.views.species import SpeciesSchema from bats_ai.core.views.temporal_annotations import ( TemporalAnnotationSchema, @@ -106,7 +107,7 @@ def create_recording( # Start generating recording as soon as created # this creates the spectrogram during the upload so it is available immediately afterwards # it will make the upload process longer but I think it's worth it. - Spectrogram.generate(recording) + recording_compute_spectrogram.delay(recording.pk) return {'message': 'Recording updated successfully', 'id': recording.pk} @@ -137,6 +138,30 @@ def update_recording(request: HttpRequest, id: int, recording_data: RecordingUpl return {'message': 'Recording updated successfully', 'id': recording.pk} +@router.delete('/{id}') +def delete_recording( + request, + id: int, +): + try: + recording = Recording.objects.get(pk=id) + + # Check if the user owns the recording or if the recording is public + if recording.owner == request.user or recording.public: + # Delete the annotation + recording.delete() + return {'message': 'Recording deleted successfully'} + else: + return { + 'error': 'Permission denied. You do not own this recording, and it is not public.' + } + + except Recording.DoesNotExist: + return {'error': 'Recording not found'} + except Annotations.DoesNotExist: + return {'error': 'Annotation not found'} + + @router.get('/') def get_recordings(request: HttpRequest, public: bool | None = None): # Filter recordings based on the owner's id or public=True @@ -150,6 +175,7 @@ def get_recordings(request: HttpRequest, public: bool | None = None): user = User.objects.get(id=recording['owner_id']) recording['owner_username'] = user.username recording['audio_file_presigned_url'] = default_storage.url(recording['audio_file']) + recording['hasSpectrogram'] = Recording.objects.get(id=recording['id']).has_spectrogram if recording['recording_location']: recording['recording_location'] = json.loads(recording['recording_location'].json) unique_users_with_annotations = ( diff --git a/client/src/api/api.ts b/client/src/api/api.ts index d7af174..177e8b9 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -26,6 +26,7 @@ export interface Recording { public: boolean; userMadeAnnotations: boolean; userAnnotations: number; + hasSpectrogram: boolean; } export interface AcousticFiles { @@ -181,11 +182,21 @@ async function uploadRecordingFile(file: File, name: string, recorded_date: stri } ); } + +interface DeletionResponse { + message?: string; + error?: string; +} async function getRecordings(getPublic=false) { - return axiosInstance.get(`/recording?public=${getPublic}`); + return axiosInstance.get(`/recording/?public=${getPublic}`); } +async function deleteRecording(id: number) { + return axiosInstance.delete(`/recording/${id}`); +} + + async function getSpectrogram(id: string) { return axiosInstance.get(`/recording/${id}/spectrogram`); } @@ -224,11 +235,11 @@ async function putTemporalAnnotation(recordingId: string, annotation: UpdateSpec } async function deleteAnnotation(recordingId: string, annotationId: number) { - return axiosInstance.delete(`/recording/${recordingId}/annotations/${annotationId}`); + return axiosInstance.delete(`/recording/${recordingId}/annotations/${annotationId}`); } async function deleteTemporalAnnotation(recordingId: string, annotationId: number) { - return axiosInstance.delete(`/recording/${recordingId}/temporal-annotations/${annotationId}`); + return axiosInstance.delete(`/recording/${recordingId}/temporal-annotations/${annotationId}`); } async function getOtherUserAnnotations(recordingId: string) { @@ -239,6 +250,7 @@ export { uploadRecordingFile, getRecordings, patchRecording, + deleteRecording, getSpectrogram, getSpectrogramCompressed, getTemporalAnnotations, diff --git a/client/src/components/AnnotationList.vue b/client/src/components/AnnotationList.vue index 589860e..88224ce 100644 --- a/client/src/components/AnnotationList.vue +++ b/client/src/components/AnnotationList.vue @@ -17,7 +17,7 @@ export default defineComponent({ emits: ['select'], setup() { const { creationType, annotationState, setAnnotationState, annotations, temporalAnnotations, selectedId, selectedType, setSelectedId } = useState(); - const tab = ref('sequence'); + const tab = ref('pulse'); const scrollToId = (id: number) => { const el = document.getElementById(`annotation-${id}`); if (el) { @@ -25,6 +25,7 @@ export default defineComponent({ } }; watch(selectedId, () => { + tab.value = selectedType.value; if (selectedId.value !== null) { scrollToId(selectedId.value); } @@ -32,6 +33,13 @@ export default defineComponent({ watch(selectedType, () => { tab.value = selectedType.value; }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const tabSwitch = (event: any) => { + // On tab switches we want to deselect the curret anntation + tab.value = event as 'sequence' | 'pulse'; + selectedType.value = event as 'sequence' | 'pulse'; + selectedId.value = null; + }; return { annotationState, @@ -42,6 +50,7 @@ export default defineComponent({ selectedType, setAnnotationState, setSelectedId, + tabSwitch, tab, }; }, @@ -51,13 +60,16 @@ export default defineComponent({