From 168cdd691c5953cedc5dd67371c770ed89d5e5a3 Mon Sep 17 00:00:00 2001 From: j Date: Wed, 22 Jan 2025 11:57:37 +0530 Subject: [PATCH 1/8] disable overscroll behavior --- source/UI/css/UI.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/UI/css/UI.css b/source/UI/css/UI.css index c13be065..2b29ba4e 100644 --- a/source/UI/css/UI.css +++ b/source/UI/css/UI.css @@ -16,6 +16,9 @@ a:hover, .OxLink:hover { blockquote { margin: 0 1.5em 0 1.5em; } +html, body { + overscroll-behavior: none; +} body { margin: 0; cursor: default; From 96d4dfe71df0fc5616a4358d0764d663b475ea30 Mon Sep 17 00:00:00 2001 From: j Date: Wed, 22 Jan 2025 17:42:30 +0530 Subject: [PATCH 2/8] allow selecting multiple annotations to get in/out range --- source/UI/css/theme.css | 3 ++ source/UI/js/Form/ArrayEditable.js | 58 ++++++++++++++++++++-- source/UI/js/Video/VideoAnnotationPanel.js | 8 +++ 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/source/UI/css/theme.css b/source/UI/css/theme.css index 83ac746d..d0413cce 100644 --- a/source/UI/css/theme.css +++ b/source/UI/css/theme.css @@ -1398,6 +1398,9 @@ Video .$themeClass .OxAnnotationFolder .OxArrayEditable .OxEditableElement.OxEditable.OxSelected { background-color: $videoAnnotationEditableSelectedBackground; } +.$themeClass .OxAnnotationFolder .OxArrayEditable .OxEditableElement.OxEditable.OxSelected.OxMultiple { + background-color: $videoAnnotationSelectedBackground; +} .$themeClass .OxAnnotationFolder .OxArrayEditable .OxEditableElement.OxSelected .OxHighlight { background-color: transparent; background-image: -moz-repeating-linear-gradient( diff --git a/source/UI/js/Form/ArrayEditable.js b/source/UI/js/Form/ArrayEditable.js index f361569d..e5b2f802 100644 --- a/source/UI/js/Form/ArrayEditable.js +++ b/source/UI/js/Form/ArrayEditable.js @@ -146,12 +146,20 @@ Ox.ArrayEditable = function(options, self) { } } + function getId(position) { + return position > -1 ? self.options.items[position].id : ''; + } + function getSelectedId() { - return self.selected > -1 ? self.options.items[self.selected].id : ''; + return getId(self.selected); + } + + function getPosition(id) { + return Ox.getIndexById(self.options.items, id); } function getSelectedPosition() { - return Ox.getIndexById(self.options.items, self.options.selected); + return getPosition(self.options.selected); } function renderItems(blur) { @@ -267,6 +275,42 @@ Ox.ArrayEditable = function(options, self) { : that.triggerEvent('selectprevious'); } } + function toggleItem(idOrPosition) { + var id, position; + if (Ox.isString(idOrPosition)) { + id = idOrPosition + position = getPosition(id); + } else { + position = idOrPosition + id = getId(position); + } + if (!Ox.isArray(self.options.selected)) { + self.options.selected = [self.options.selected] + } + if (!Ox.isArray(self.selected)) { + self.selected = [self.selected] + } + if (self.options.selected.includes(id)) { + self.options.selected.pop(id) + self.selected.pop(position) + } else { + self.options.selected.push(id) + self.selected.push(position) + } + that.find('.OxSelected').removeClass('OxSelected').removeClass('OxMultiple'); + that.find('.OxGroup').removeClass('OxGroup') + if (self.options.selected.length == 1) { + self.options.selected = self.options.selected[0] + self.selected = self.selected[0] + self.$items[self.selected].addClass('OxSelected'); + self.options.highlightGroup && that.updateItemGroup() + } else { + Ox.forEach(self.selected, function(selected) { + self.$items[selected].addClass('OxSelected').addClass('OxMultiple'); + }) + } + triggerSelectEvent(); + } function selectItem(idOrPosition) { if (Ox.isString(idOrPosition)) { @@ -327,8 +371,14 @@ Ox.ArrayEditable = function(options, self) { position = $element.data('position'); // if clicked on an element if (position != self.selected) { - // select another item - selectItem(position); + if (e.shiftKey) { + // add/remove item from multiple selection + document.getSelection().removeAllRanges(); + toggleItem(position); + } else { + // select another item + selectItem(position); + } } else if (e.metaKey) { // or deselect current item selectNone(); diff --git a/source/UI/js/Video/VideoAnnotationPanel.js b/source/UI/js/Video/VideoAnnotationPanel.js index 5ce19326..c9c22801 100644 --- a/source/UI/js/Video/VideoAnnotationPanel.js +++ b/source/UI/js/Video/VideoAnnotationPanel.js @@ -1350,6 +1350,14 @@ Ox.VideoAnnotationPanel = function(options, self) { if (Ox.isUndefined(data)) { // doubleclick on small timeline data = getAnnotation(); + } else if (Ox.isArray(data.id)) { + var range = data.id.map(id => { + return Ox.getObjectById(self.annotations, id) + }) + data['in'] = Ox.min(range.map(annotation => { return annotation["in"]; })) + data['out'] = Ox.max(range.map(annotation => { return annotation["out"]; })) + setPoint('in', data['in'], true); + setPoint('out', data.out, true); } else if (!data.id && Ox.$elements[that.oxid]) { // focus only if in the dom that.gainFocus(); From 03eb3d4a569a4c23c0efe86e571f55e7d1484d1e Mon Sep 17 00:00:00 2001 From: j Date: Fri, 24 Jan 2025 17:10:36 +0530 Subject: [PATCH 3/8] select multple annotations, option to pass confirm delete dialog --- source/UI/js/Form/ArrayEditable.js | 55 ++++++++++++++-------- source/UI/js/Video/AnnotationFolder.js | 2 + source/UI/js/Video/AnnotationPanel.js | 2 + source/UI/js/Video/VideoAnnotationPanel.js | 2 + 4 files changed, 42 insertions(+), 19 deletions(-) diff --git a/source/UI/js/Form/ArrayEditable.js b/source/UI/js/Form/ArrayEditable.js index e5b2f802..7a1a0637 100644 --- a/source/UI/js/Form/ArrayEditable.js +++ b/source/UI/js/Form/ArrayEditable.js @@ -25,6 +25,7 @@ Ox.ArrayEditable = function(options, self) { var that = Ox.Element({}, self) .defaults({ clickLink: null, + confirmDeleteDialog: null, editable: true, format: null, getSortValue: null, @@ -120,19 +121,27 @@ Ox.ArrayEditable = function(options, self) { // it is possible to delete an (empty-value) item // by selecting another one id = id || self.options.selected; - var position = Ox.getIndexById(self.options.items, id) - if (self.options.editable) { - self.options.items.splice(position, 1); - renderItems(); - that.triggerEvent('delete', {id: id}); - self.editing = false; - if (self.options.selected == id) { - self.selected = -1; - self.options.selected = ''; - } else if (self.options.selected) { - self.selected = Ox.getIndexById(self.options.item, self.options.selected) + + (self.options.confirmDeleteDialog ? self.options.confirmDeleteDialog: Ox.noop)({ + items: Ox.isArray(id) ? id : [id], + }, function(result) { + if (self.options.editable) { + (Ox.isArray(id) ? id : [id]).forEach(id => { + var position = Ox.getIndexById(self.options.items, id) + self.options.items.splice(position, 1); + that.triggerEvent('delete', {id: id}); + }) + renderItems(); + self.editing = false; + if (self.options.selected == id) { + self.selected = -1; + self.options.selected = ''; + } else if (self.options.selected) { + self.selected = Ox.getIndexById(self.options.item, self.options.selected) + } } - } + }) + } function doubleclick(e) { @@ -290,12 +299,20 @@ Ox.ArrayEditable = function(options, self) { if (!Ox.isArray(self.selected)) { self.selected = [self.selected] } - if (self.options.selected.includes(id)) { - self.options.selected.pop(id) - self.selected.pop(position) - } else { - self.options.selected.push(id) - self.selected.push(position) + if (!self.options.selected.includes(id)) { + var minPos, maxPos; + if (Ox.min(self.selected) > position) { + minPos = position + maxPos = Ox.min(self.selected) + } else { + minPos = Ox.max(self.selected) + 1 + maxPos = position + 1 + } + Ox.range(minPos, maxPos).forEach(pos => { + self.selected.push(pos) + self.options.selected.push(getId(pos)) + }) + } that.find('.OxSelected').removeClass('OxSelected').removeClass('OxMultiple'); that.find('.OxGroup').removeClass('OxGroup') @@ -371,7 +388,7 @@ Ox.ArrayEditable = function(options, self) { position = $element.data('position'); // if clicked on an element if (position != self.selected) { - if (e.shiftKey) { + if (e.shiftKey && self.selected != -1) { // add/remove item from multiple selection document.getSelection().removeAllRanges(); toggleItem(position); diff --git a/source/UI/js/Video/AnnotationFolder.js b/source/UI/js/Video/AnnotationFolder.js index 4b04dcd4..5439ebed 100644 --- a/source/UI/js/Video/AnnotationFolder.js +++ b/source/UI/js/Video/AnnotationFolder.js @@ -35,6 +35,7 @@ Ox.AnnotationFolder = function(options, self) { var that = Ox.Element({}, self) .defaults({ clickLink: null, + confirmDeleteDialog: null, collapsed: false, editable: false, highlight: '', @@ -281,6 +282,7 @@ Ox.AnnotationFolder = function(options, self) { } self.$annotations = Ox.ArrayEditable(Ox.extend({ clickLink: self.options.clickLink, + confirmDeleteDialog: self.options.confirmDeleteDialog, editable: self.options.editable, getSortValue: self.options.type == 'text' ? function(value) { diff --git a/source/UI/js/Video/AnnotationPanel.js b/source/UI/js/Video/AnnotationPanel.js index 7e03f779..5f81c56a 100644 --- a/source/UI/js/Video/AnnotationPanel.js +++ b/source/UI/js/Video/AnnotationPanel.js @@ -48,6 +48,7 @@ Ox.AnnotationPanel = function(options, self) { .defaults({ calendarSize: 256, clickLink: null, + confirmDeleteDialog: null, editable: false, enableExport: false, enableImport: false, @@ -371,6 +372,7 @@ Ox.AnnotationPanel = function(options, self) { self.$folder[index] = Ox.AnnotationFolder( Ox.extend({ clickLink: self.options.clickLink, + confirmDeleteDialog: self.options.confirmDeleteDialog, collapsed: !self.options.showLayers[layer.id], editable: self.options.editable, highlight: getHighlight(layer.id), diff --git a/source/UI/js/Video/VideoAnnotationPanel.js b/source/UI/js/Video/VideoAnnotationPanel.js index c9c22801..08585a7c 100644 --- a/source/UI/js/Video/VideoAnnotationPanel.js +++ b/source/UI/js/Video/VideoAnnotationPanel.js @@ -61,6 +61,7 @@ Ox.VideoAnnotationPanel = function(options, self) { censoredIcon: '', censoredTooltip: '', clickLink: null, + confirmDeleteDialog: null, cuts: [], duration: 0, enableDownload: false, @@ -844,6 +845,7 @@ Ox.VideoAnnotationPanel = function(options, self) { autocomplete: self.options.autocomplete, calendarSize: self.options.annotationsCalendarSize, clickLink: self.options.clickLink, + confirmDeleteDialog: self.options.confirmDeleteDialog, editable: true, enableExport: self.options.enableExport, enableImport: self.options.enableImport, From 6341e64a649794bdf01ba616230e897abe208f56 Mon Sep 17 00:00:00 2001 From: j Date: Fri, 24 Jan 2025 16:23:19 +0530 Subject: [PATCH 4/8] add transcribe option --- source/UI/js/Video/AnnotationPanel.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/source/UI/js/Video/AnnotationPanel.js b/source/UI/js/Video/AnnotationPanel.js index 5f81c56a..1677697f 100644 --- a/source/UI/js/Video/AnnotationPanel.js +++ b/source/UI/js/Video/AnnotationPanel.js @@ -52,6 +52,7 @@ Ox.AnnotationPanel = function(options, self) { editable: false, enableExport: false, enableImport: false, + enableTranscribe: false, highlight: '', highlightAnnotations: 'none', highlightLayer: '*', @@ -304,7 +305,11 @@ Ox.AnnotationPanel = function(options, self) { {}, {id: 'import', title: Ox._('Import Annotations...'), disabled: !self.options.enableImport}, {id: 'export', title: Ox._('Export Annotations...'), disabled: !self.options.enableExport}, - ] + ].concat( + self.options.enableTranscribe ? [ + {id: 'transcribe', title: Ox._('Transcribe Audio...')}, + ]: [] + ) ), maxWidth: 256, style: 'square', @@ -352,6 +357,8 @@ Ox.AnnotationPanel = function(options, self) { // ... } else if (data.id == 'showentityinfo') { that.triggerEvent('showentityinfo', annotation.entity); + } else if (data.id == 'transcribe') { + that.triggerEvent('transcribe'); } else if (data.id == 'undo') { // ... } From 4cde39b1825e91f62236dff3fc5fdb159c41bf6a Mon Sep 17 00:00:00 2001 From: j Date: Fri, 24 Jan 2025 19:59:56 +0530 Subject: [PATCH 5/8] hook up transcribe option --- source/UI/js/Video/AnnotationPanel.js | 6 +++--- source/UI/js/Video/VideoAnnotationPanel.js | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/source/UI/js/Video/AnnotationPanel.js b/source/UI/js/Video/AnnotationPanel.js index 1677697f..658dc9f4 100644 --- a/source/UI/js/Video/AnnotationPanel.js +++ b/source/UI/js/Video/AnnotationPanel.js @@ -307,7 +307,7 @@ Ox.AnnotationPanel = function(options, self) { {id: 'export', title: Ox._('Export Annotations...'), disabled: !self.options.enableExport}, ].concat( self.options.enableTranscribe ? [ - {id: 'transcribe', title: Ox._('Transcribe Audio...')}, + {id: 'transcribeaudio', title: Ox._('Transcribe Audio...')}, ]: [] ) ), @@ -357,8 +357,8 @@ Ox.AnnotationPanel = function(options, self) { // ... } else if (data.id == 'showentityinfo') { that.triggerEvent('showentityinfo', annotation.entity); - } else if (data.id == 'transcribe') { - that.triggerEvent('transcribe'); + } else if (data.id == 'transcribeaudio') { + that.triggerEvent('transcribeaudio'); } else if (data.id == 'undo') { // ... } diff --git a/source/UI/js/Video/VideoAnnotationPanel.js b/source/UI/js/Video/VideoAnnotationPanel.js index 08585a7c..64e00a7c 100644 --- a/source/UI/js/Video/VideoAnnotationPanel.js +++ b/source/UI/js/Video/VideoAnnotationPanel.js @@ -67,6 +67,7 @@ Ox.VideoAnnotationPanel = function(options, self) { enableDownload: false, enableImport: false, enableExport: false, + enableTranscribe: false, enableSetPosterFrame: false, enableSubtitles: false, find: '', @@ -849,6 +850,7 @@ Ox.VideoAnnotationPanel = function(options, self) { editable: true, enableExport: self.options.enableExport, enableImport: self.options.enableImport, + enableTranscribe: self.options.enableTranscribe, highlight: self.options.find, highlightAnnotations: self.options.annotationsHighlight, highlightLayer: self.options.findLayer, @@ -951,6 +953,9 @@ Ox.VideoAnnotationPanel = function(options, self) { that.triggerEvent('showentityinfo', data); }, submit: submitAnnotation, + transcribeaudio: function() { + that.triggerEvent('transcribeaudio'); + }, toggle: toggleAnnotations, togglecalendar: function(data) { self.options.showAnnotationsCalendar = !data.collapsed; From 8e760c3959aa7dadac8683d88c3a065205460173 Mon Sep 17 00:00:00 2001 From: j Date: Fri, 24 Jan 2025 23:31:51 +0530 Subject: [PATCH 6/8] no special color for multiple --- source/UI/css/theme.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/UI/css/theme.css b/source/UI/css/theme.css index d0413cce..83ac746d 100644 --- a/source/UI/css/theme.css +++ b/source/UI/css/theme.css @@ -1398,9 +1398,6 @@ Video .$themeClass .OxAnnotationFolder .OxArrayEditable .OxEditableElement.OxEditable.OxSelected { background-color: $videoAnnotationEditableSelectedBackground; } -.$themeClass .OxAnnotationFolder .OxArrayEditable .OxEditableElement.OxEditable.OxSelected.OxMultiple { - background-color: $videoAnnotationSelectedBackground; -} .$themeClass .OxAnnotationFolder .OxArrayEditable .OxEditableElement.OxSelected .OxHighlight { background-color: transparent; background-image: -moz-repeating-linear-gradient( From 01596fa174e8772790214237069ee510f3b4318c Mon Sep 17 00:00:00 2001 From: j Date: Sat, 25 Jan 2025 10:27:45 +0530 Subject: [PATCH 7/8] fix multiple id selection in VideoAnnotationPanel chain --- source/UI/js/Video/AnnotationFolder.js | 50 ++++++++++++++-------- source/UI/js/Video/VideoAnnotationPanel.js | 8 ---- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/source/UI/js/Video/AnnotationFolder.js b/source/UI/js/Video/AnnotationFolder.js index 5439ebed..2284f36d 100644 --- a/source/UI/js/Video/AnnotationFolder.js +++ b/source/UI/js/Video/AnnotationFolder.js @@ -581,26 +581,40 @@ Ox.AnnotationFolder = function(options, self) { function selectAnnotation(data) { if (self.loaded) { - var item = Ox.getObjectById(self.options.items, data.id); - self.options.selected = item ? data.id : ''; - if (self.widget) { - if (self.options.type == 'event') { - self.$calendar.options({ - selected: item && isDefined(item) ? item.event.id : '' - }) - .panToEvent(); - } else { - self.$map.options({ - selected: item && isDefined(item) ? item.place.id : '' - }) - .panToPlace(); + var extendedData; + if (Ox.isArray(data.id) && data.id.length) { + var items = data.id.map(id => { + return Ox.getObjectById(self.options.items, id); + }) + self.options.selected = data.id; + extendedData = Ox.extend(data, { + 'in': Ox.min(items.map(item => { return item["in"]; })), + out: Ox.max(items.map(item => { return item["out"]; })), + layer: self.options.id + }); + } else { + var item = Ox.getObjectById(self.options.items, data.id) + self.options.selected = item ? data.id : ''; + if (self.widget) { + if (self.options.type == 'event') { + self.$calendar.options({ + selected: item && isDefined(item) ? item.event.id : '' + }) + .panToEvent(); + } else { + self.$map.options({ + selected: item && isDefined(item) ? item.place.id : '' + }) + .panToPlace(); + } } + extendedData = Ox.extend(data, item ? { + 'in': item['in'], + out: item.out, + layer: self.options.id + } : {}); } - that.triggerEvent('select', Ox.extend(data, item ? { - 'in': item['in'], - out: item.out, - layer: self.options.id - } : {})); + that.triggerEvent('select', extendedData) } } diff --git a/source/UI/js/Video/VideoAnnotationPanel.js b/source/UI/js/Video/VideoAnnotationPanel.js index 64e00a7c..3d52533a 100644 --- a/source/UI/js/Video/VideoAnnotationPanel.js +++ b/source/UI/js/Video/VideoAnnotationPanel.js @@ -1357,14 +1357,6 @@ Ox.VideoAnnotationPanel = function(options, self) { if (Ox.isUndefined(data)) { // doubleclick on small timeline data = getAnnotation(); - } else if (Ox.isArray(data.id)) { - var range = data.id.map(id => { - return Ox.getObjectById(self.annotations, id) - }) - data['in'] = Ox.min(range.map(annotation => { return annotation["in"]; })) - data['out'] = Ox.max(range.map(annotation => { return annotation["out"]; })) - setPoint('in', data['in'], true); - setPoint('out', data.out, true); } else if (!data.id && Ox.$elements[that.oxid]) { // focus only if in the dom that.gainFocus(); From 6e1e2be9c41dd75e9acfc5acaf300327b3944b19 Mon Sep 17 00:00:00 2001 From: j Date: Thu, 30 Jan 2025 16:59:35 +0530 Subject: [PATCH 8/8] no confirmation dialog for empty annotations --- source/UI/js/Form/ArrayEditable.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/UI/js/Form/ArrayEditable.js b/source/UI/js/Form/ArrayEditable.js index 7a1a0637..11f62ff9 100644 --- a/source/UI/js/Form/ArrayEditable.js +++ b/source/UI/js/Form/ArrayEditable.js @@ -117,12 +117,12 @@ Ox.ArrayEditable = function(options, self) { self.selected = getSelectedPosition(); - function deleteItem(id) { + function deleteItem(id, empty) { // it is possible to delete an (empty-value) item // by selecting another one id = id || self.options.selected; - (self.options.confirmDeleteDialog ? self.options.confirmDeleteDialog: Ox.noop)({ + (self.options.confirmDeleteDialog && !empty ? self.options.confirmDeleteDialog: Ox.noop)({ items: Ox.isArray(id) ? id : [id], }, function(result) { if (self.options.editable) { @@ -437,7 +437,7 @@ Ox.ArrayEditable = function(options, self) { var item = Ox.getObjectById(self.options.items, id); Ox.Log('AE', 'submitItem', id, item) if (value === '') { - deleteItem(item.id); + deleteItem(item.id, true); } else { that.triggerEvent(item.value === value ? 'blur' : 'submit', { id: item.id,