diff --git a/cvat/apps/engine/static/engine/js/annotationUI.js b/cvat/apps/engine/static/engine/js/annotationUI.js index e2bfce72..eefd6ca0 100644 --- a/cvat/apps/engine/static/engine/js/annotationUI.js +++ b/cvat/apps/engine/static/engine/js/annotationUI.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ -/* exported callAnnotationUI translateSVGPos blurAllElements drawBoxSize */ +/* exported callAnnotationUI translateSVGPos blurAllElements drawBoxSize copyToClipboard */ "use strict"; function callAnnotationUI(jid) { @@ -55,8 +55,46 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) { z_order: job.z_order, id: job.jobid }, + search: { + value: window.location.search, + + set: function(name, value) { + let searchParams = new URLSearchParams(this.value); + + if (typeof value === 'undefined' || value === null) { + if (searchParams.has(name)) { + searchParams.delete(name); + } + } + else searchParams.set(name, value); + this.value = `${searchParams.toString()}`; + }, + + get: function(name) { + try { + let decodedURI = decodeURIComponent(this.value); + let urlSearchParams = new URLSearchParams(decodedURI); + if (urlSearchParams.has(name)) { + return urlSearchParams.get(name); + } + else return null; + } + catch (error) { + showMessage('Bad URL has been found'); + this.value = window.location.href; + return null; + } + }, + + toString: function() { + return `${window.location.origin}/?${this.value}`; + } + } }; + // Remove external search parameters from url + window.history.replaceState(null, null, `${window.location.origin}/?id=${job.jobid}`); + window.cvat.config = new Config(); // Setup components @@ -137,7 +175,7 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) { playerModel.subscribe(shapeBufferView); playerModel.subscribe(shapeGrouperView); playerModel.subscribe(polyshapeEditorView); - playerModel.shift(getURISearchParameter('frame') || 0, true); + playerModel.shift(window.cvat.search.get('frame') || 0, true); let shortkeys = window.cvat.config.shortkeys; @@ -185,6 +223,16 @@ function buildAnnotationUI(job, shapeData, loadJobEvent) { }); } + +function copyToClipboard(text) { + let tempInput = $(""); + $("body").append(tempInput); + tempInput.prop('value', text).select(); + document.execCommand("copy"); + tempInput.remove(); +} + + function setupFrameFilters() { let brightnessRange = $('#playerBrightnessRange'); let contrastRange = $('#playerContrastRange'); diff --git a/cvat/apps/engine/static/engine/js/base.js b/cvat/apps/engine/static/engine/js/base.js index a164e3cd..bfc937c4 100644 --- a/cvat/apps/engine/static/engine/js/base.js +++ b/cvat/apps/engine/static/engine/js/base.js @@ -4,7 +4,7 @@ * SPDX-License-Identifier: MIT */ -/* exported confirm showMessage showOverlay dumpAnnotationRequest getURISearchParameter setURISearchParameter */ +/* exported confirm showMessage showOverlay dumpAnnotationRequest */ "use strict"; Math.clamp = function(x, min, max) { @@ -160,45 +160,6 @@ function dumpAnnotationRequest(dumpButton, taskID) { } } - -function setURISearchParameter(name, value) { - let searchParams = new URLSearchParams(window.location.search); - if (typeof value === 'undefined' || value === null) { - if (searchParams.has(name)) { - searchParams.delete(name); - } - } - else searchParams.set(name, value); - - window.history.replaceState(null, null, `?${searchParams.toString()}`); -} - - -function resetURISearchParameters() { - let searchParams = new URLSearchParams(); - searchParams.set('id', window.cvat.job.id); - window.history.replaceState(null, null, `?${searchParams.toString()}`); -} - - -function getURISearchParameter(name) { - let decodedURI = ''; - try { - decodedURI = decodeURIComponent(window.location.search); - } - catch (error) { - showMessage('Bad URL has been found'); - resetURISearchParameters(); - } - - let urlSearchParams = new URLSearchParams(decodedURI); - if (urlSearchParams.has(name)) { - return urlSearchParams.get(name); - } - else return null; -} - - /* These HTTP methods do not require CSRF protection */ function csrfSafeMethod(method) { return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); diff --git a/cvat/apps/engine/static/engine/js/player.js b/cvat/apps/engine/static/engine/js/player.js index 2dead4bd..9c3c5d78 100644 --- a/cvat/apps/engine/static/engine/js/player.js +++ b/cvat/apps/engine/static/engine/js/player.js @@ -647,6 +647,7 @@ class PlayerView { this._frameNumber = $('#frameNumber'); this._playerGridPattern = $('#playerGridPattern'); this._playerGridPath = $('#playerGridPath'); + this._contextMenuUI = $('#playerContextMenu'); $('*').on('mouseup', () => this._controller.frameMouseUp()); this._playerUI.on('wheel', (e) => this._controller.zoom(e)); @@ -763,6 +764,38 @@ class PlayerView { this._multiplePrevButtonUI.find('polygon').append($(document.createElementNS('http://www.w3.org/2000/svg', 'title')) .html(`${shortkeys['backward_frame'].view_value} - ${shortkeys['backward_frame'].description}`)); + + this._contextMenuUI.click((e) => { + $('.custom-menu').hide(100); + switch($(e.target).attr("action")) { + case "job_url": { + window.cvat.search.set('frame', null); + window.cvat.search.set('filter', null); + copyToClipboard(window.cvat.search.toString()); + break; + } + case "frame_url": + window.cvat.search.set('frame', window.cvat.player.frames.current); + window.cvat.search.set('filter', null); + copyToClipboard(window.cvat.search.toString()); + window.cvat.search.set('frame', null); + break; + } + }); + + this._playerContentUI.on('contextmenu.playerContextMenu', (e) => { + $('.custom-menu').hide(100); + this._contextMenuUI.finish().show(100).offset({ + top: e.pageY - 10, + left: e.pageX - 10, + }); + e.preventDefault(); + }); + + this._playerContentUI.on('mousedown.playerContextMenu', () => { + $('.custom-menu').hide(100); + }); + playerModel.subscribe(this); } @@ -780,7 +813,6 @@ class PlayerView { this._loadingUI.addClass('hidden'); if (this._playerBackgroundUI.css('background-image').slice(5,-2) != image.src) { this._playerBackgroundUI.css('background-image', 'url(' + '"' + image.src + '"' + ')'); - setURISearchParameter('frame', frames.current); } if (model.playing) { diff --git a/cvat/apps/engine/static/engine/js/shapeCollection.js b/cvat/apps/engine/static/engine/js/shapeCollection.js index 28091b6b..3cf28415 100644 --- a/cvat/apps/engine/static/engine/js/shapeCollection.js +++ b/cvat/apps/engine/static/engine/js/shapeCollection.js @@ -988,6 +988,10 @@ class ShapeCollectionController { get filterController() { return this._filterController; } + + get activeShape() { + return this._model.activeShape; + } } class ShapeCollectionView { @@ -1103,7 +1107,10 @@ class ShapeCollectionView { }); this._frameContent.on('mousemove', function(e) { - if (e.ctrlKey || e.which === 2 || e.target.classList.contains('svg_select_points')) { + if (e.ctrlKey || e.shiftKey || e.which === 2 || e.target.classList.contains('svg_select_points')) { + if (e.shiftKey) { + this._controller.resetActive(); + } return; } @@ -1117,9 +1124,20 @@ class ShapeCollectionView { $('#shapeContextMenu li').click((e) => { let menu = $('#shapeContextMenu'); - menu.hide(100); + $('.custom-menu').hide(100); switch($(e.target).attr("action")) { + case "object_url": { + let active = this._controller.activeShape; + if (active) { + window.cvat.search.set('frame', window.cvat.player.frames.current); + window.cvat.search.set('filter', `*[id="${active.id}"]`); + copyToClipboard(window.cvat.search.toString()); + window.cvat.search.set('frame', null); + window.cvat.search.set('filter', null); + } + break; + } case "change_color": this._controller.switchActiveColor(); break; @@ -1162,7 +1180,7 @@ class ShapeCollectionView { $('#pointContextMenu li').click((e) => { let menu = $('#pointContextMenu'); let idx = +menu.attr('point_idx'); - menu.hide(100); + $('.custom-menu').hide(100); switch($(e.target).attr("action")) { case "remove_point": diff --git a/cvat/apps/engine/static/engine/js/shapeFilter.js b/cvat/apps/engine/static/engine/js/shapeFilter.js index 36d86d2c..06310e8a 100644 --- a/cvat/apps/engine/static/engine/js/shapeFilter.js +++ b/cvat/apps/engine/static/engine/js/shapeFilter.js @@ -113,11 +113,10 @@ class FilterView { let value = $.trim(e.target.value); if (this._controller.updateFilter(value, false)) { this._filterString.css('color', 'green'); - setURISearchParameter('filter', value || null); } else { this._filterString.css('color', 'red'); - setURISearchParameter('filter', null); + this._controller.updateFilter('', false); } }); @@ -129,17 +128,15 @@ class FilterView { this._resetFilterButton.on('click', () => { this._filterString.prop('value', ''); this._controller.updateFilter('', false); - setURISearchParameter('filter', null); }); - if (getURISearchParameter('filter')) { - let value = getURISearchParameter('filter'); - this._filterString.prop('value', value); - if (this._controller.updateFilter(value, true)) { + let initialFilter = window.cvat.search.get('filter'); + if (initialFilter) { + this._filterString.prop('value', initialFilter); + if (this._controller.updateFilter(initialFilter, true)) { this._filterString.css('color', 'green'); } else { - setURISearchParameter('filter', null); this._filterString.prop('value', ''); this._filterString.css('color', 'red'); } diff --git a/cvat/apps/engine/static/engine/js/shapes.js b/cvat/apps/engine/static/engine/js/shapes.js index 4207c5e5..c78cc729 100644 --- a/cvat/apps/engine/static/engine/js/shapes.js +++ b/cvat/apps/engine/static/engine/js/shapes.js @@ -1492,8 +1492,7 @@ class ShapeView extends Listener { // Setup context menu this._uis.shape.on('mousedown.contextMenu', (e) => { if (e.which === 1) { - this._shapeContextMenu.hide(100); - this._pointContextMenu.hide(100); + $('.custom-menu').hide(100); } if (e.which === 3) { e.stopPropagation(); @@ -1501,7 +1500,7 @@ class ShapeView extends Listener { }); this._uis.shape.on('contextmenu.contextMenu', (e) => { - this._pointContextMenu.hide(100); + $('.custom-menu').hide(100); let type = this._controller.type.split('_'); if (type[0] === 'interpolation') { this._shapeContextMenu.find('.interpolationItem').removeClass('hidden'); @@ -1553,8 +1552,7 @@ class ShapeView extends Listener { this._flags.editable = false; } - this._pointContextMenu.hide(100); - this._shapeContextMenu.hide(100); + $('.custom-menu').hide(100); } @@ -2829,7 +2827,7 @@ class PolyShapeView extends ShapeView { point = $(point); point.on('contextmenu.contextMenu', (e) => { - this._shapeContextMenu.hide(100); + $('.custom-menu').hide(100); this._pointContextMenu.attr('point_idx', point.index()); this._pointContextMenu.attr('dom_point_id', point.attr('id')); diff --git a/cvat/apps/engine/templates/engine/annotation.html b/cvat/apps/engine/templates/engine/annotation.html index 0042b798..67e6b137 100644 --- a/cvat/apps/engine/templates/engine/annotation.html +++ b/cvat/apps/engine/templates/engine/annotation.html @@ -77,7 +77,9 @@ + + + diff --git a/tests/eslintrc.conf.js b/tests/eslintrc.conf.js index cd8affdc..57614552 100644 --- a/tests/eslintrc.conf.js +++ b/tests/eslintrc.conf.js @@ -44,13 +44,12 @@ module.exports = { 'translateSVGPos': true, 'blurAllElements': true, 'drawBoxSize': true, + 'copyToClipboard': true, // from base.js 'showMessage': true, 'showOverlay': true, 'confirm': true, 'dumpAnnotationRequest': true, - 'getURISearchParameter': true, - 'setURISearchParameter': true, // from shapeCollection.js 'ShapeCollectionModel': true, 'ShapeCollectionController': true,