diff --git a/api/main/rest/media.py b/api/main/rest/media.py index 3d4638b03..ee9eb6c6c 100644 --- a/api/main/rest/media.py +++ b/api/main/rest/media.py @@ -242,7 +242,7 @@ def _create_media(project, params, user, use_rq=False): gid = params.get("gid", None) uid = params.get("uid", None) new_attributes = params.get("attributes", None) - url = params.get("url") + url = params.get("url", None) elemental_id = params.get("elemental_id", uuid4()) if gid is not None: gid = str(gid) @@ -317,15 +317,18 @@ def _create_media(project, params, user, use_rq=False): # Set up S3 client. tator_store = get_tator_store(project_obj.bucket) - reference_only = params.get("reference_only", 0) == 1 - if use_rq: - push_job( - "image_jobs", - main._import_image._import_image, - args=(name, url, thumbnail_url, media_obj.id, reference_only), - ) - else: - main._import_image._import_image(name, url, thumbnail_url, media_obj.id, reference_only) + if url: + reference_only = params.get("reference_only", 0) == 1 + if use_rq: + push_job( + "image_jobs", + main._import_image._import_image, + args=(name, url, thumbnail_url, media_obj.id, reference_only), + ) + else: + main._import_image._import_image( + name, url, thumbnail_url, media_obj.id, reference_only + ) else: # Create the media object. diff --git a/api/main/rest/state.py b/api/main/rest/state.py index a78bbae6e..450d31db1 100644 --- a/api/main/rest/state.py +++ b/api/main/rest/state.py @@ -678,9 +678,19 @@ def delete_qs(self, params, qs): f"Pedantic mode is enabled. Can not edit prior object {state.pk}, must only edit latest mark on version." f"Object is mark {state.mark} of {state.latest_mark} for {state.version.name}/{state.elemental_id}" ) + old_media = state.media.all() + old_localizations = state.localizations.all() state.pk = None state.variant_deleted = True + origin_datetime = state.created_datetime state.save() + found_it = State.objects.get(pk=state.pk) + # Keep original creation time + found_it.created_datetime = origin_datetime + found_it.save() + found_it.media.set(old_media) + found_it.localizations.set(old_localizations) + found_it.save() obj_id = state.pk log_changes(state, state.model_dict, state.project, self.request.user) qs = Localization.objects.filter(pk__in=delete_localizations) diff --git a/api/main/schema/components/media.py b/api/main/schema/components/media.py index dc2aa68e6..5131419af 100644 --- a/api/main/schema/components/media.py +++ b/api/main/schema/components/media.py @@ -179,7 +179,7 @@ "type": "string", }, "reference_only": { - "description": "Do not import the media resources into Tator. This causes the image to be accessed at the supplied URL upon access in the UI. Valid for images only.", + "description": "Do not import the media resources into Tator. This causes the image to be accessed at the supplied URL upon access in the UI. Attempts loading the image from the URL, but fall back to other records if the URL is not accessible.", "type": "integer", "default": 0, "maximum": 1, diff --git a/api/main/tests.py b/api/main/tests.py index 2bb489c5e..e39e261d5 100644 --- a/api/main/tests.py +++ b/api/main/tests.py @@ -2997,6 +2997,24 @@ def setUp(self): self.patch_json = {"name": "image1"} memberships_to_rowp(self.project.pk, force=False, verbose=False) + def test_empty_image(self): + unique_string_attr_val = str(uuid4()) + body = [ + { + "type": self.entity_type.pk, + "section": "asdf", + "name": "asdf", + "md5": "asdf", + "attributes": {"String Test": unique_string_attr_val}, + } + ] + response = self.client.post(f"/rest/Medias/{self.project.pk}", body, format="json") + media_id1 = response.data["id"][0] + + response = self.client.get(f"/rest/Media/{media_id1}") + assert response.data["attributes"]["String Test"] == unique_string_attr_val + assert response.data["media_files"] in [None, {}] + class LocalizationBoxTestCase( TatorTransactionTest, diff --git a/scripts/packages/tator-js b/scripts/packages/tator-js index 4369b2c00..3183fb023 160000 --- a/scripts/packages/tator-js +++ b/scripts/packages/tator-js @@ -1 +1 @@ -Subproject commit 4369b2c000e0b0c27d1a89c6b2eb9b19e59d21bc +Subproject commit 3183fb0238634f1b5423e885bb71bdad81c322ac diff --git a/scripts/packages/tator-py b/scripts/packages/tator-py index 7d5d58524..758888cd9 160000 --- a/scripts/packages/tator-py +++ b/scripts/packages/tator-py @@ -1 +1 @@ -Subproject commit 7d5d58524bcc7e556eeb5e8f95b93434742870cf +Subproject commit 758888cd9929ff0f64a853fe45f642f56d56f0e1 diff --git a/ui/src/js/annotation/annotation-multi.js b/ui/src/js/annotation/annotation-multi.js index 8c4918fa9..cac49b9fe 100644 --- a/ui/src/js/annotation/annotation-multi.js +++ b/ui/src/js/annotation/annotation-multi.js @@ -829,6 +829,11 @@ export class AnnotationMulti extends TatorElement { this._videoTimeline.init(0, this._timeStore.getLastGlobalFrame()); this._entityTimeline.init(0, this._timeStore.getLastGlobalFrame()); + this._displayTimelineLabels = true; + this._entityTimeline.showFocus( + this._displayTimelineLabels, + this._videos[this._primaryVideoIndex].currentFrame() + ); this._slider.setAttribute("min", 0); this._slider.setAttribute("max", this._timeStore.getLastGlobalFrame()); diff --git a/ui/src/js/annotation/annotation-player.js b/ui/src/js/annotation/annotation-player.js index ec8df0fbb..b456aeb83 100644 --- a/ui/src/js/annotation/annotation-player.js +++ b/ui/src/js/annotation/annotation-player.js @@ -940,6 +940,11 @@ export class AnnotationPlayer extends TatorElement { this._videoTimeline.init(0, this._timeStore.getLastGlobalFrame()); this._entityTimeline.init(0, this._timeStore.getLastGlobalFrame()); + this._displayTimelineLabels = true; + this._entityTimeline.showFocus( + this._displayTimelineLabels, + this._video.currentFrame() + ); this._slider.setAttribute("min", 0); this._slider.setAttribute("max", this._timeStore.getLastGlobalFrame()); diff --git a/ui/src/js/annotation/entity-timeline.js b/ui/src/js/annotation/entity-timeline.js index 54f704864..43fdb7900 100644 --- a/ui/src/js/annotation/entity-timeline.js +++ b/ui/src/js/annotation/entity-timeline.js @@ -1581,20 +1581,7 @@ export class EntityTimeline extends BaseTimeline { showFocus(display, currentFrame) { if (display) { if (currentFrame != null) { - var timelineSpan = this._maxFrame - this._minFrame; - var window = Math.floor(timelineSpan * 0.1); - if (window < 25) { - window = 25; - } - var minFrame = currentFrame - window; - if (minFrame < this._minFrame) { - minFrame = this._minFrame; - } - var maxFrame = currentFrame + window; - if (maxFrame > this._maxFrame) { - maxFrame = this._maxFrame; - } - this._mainBrushWindow = [minFrame, maxFrame]; + this._mainBrushWindow = [this._minFrame, this._maxFrame]; } this._focusTimelineDiv.style.display = "block"; diff --git a/ui/src/js/project-detail/project-text.js b/ui/src/js/project-detail/project-text.js index 595a3cd5a..46bfb58bf 100644 --- a/ui/src/js/project-detail/project-text.js +++ b/ui/src/js/project-detail/project-text.js @@ -1,5 +1,24 @@ import { TatorElement } from "../components/tator-element.js"; +function sanitizeHTML(input) { + // Create a DOM parser + const parser = new DOMParser(); + // Parse the input as HTML + const doc = parser.parseFromString(input, "text/html"); + // Check if the input was parsed as HTML (not just plain text) + const isHTML = doc.body.children.length > 0; + if (isHTML) { + // Remove all