From 3c94f44a9943bef627d55bf782134d14f29a91f2 Mon Sep 17 00:00:00 2001 From: rlx <0x0073@0x2620.org> Date: Sat, 4 Feb 2012 08:58:46 +0000 Subject: [PATCH] fix bugs in video editor; begin to implement new list map mode that allows for defining/clearing places --- source/Ox.UI/css/Ox.UI.css | 11 +- source/Ox.UI/js/Calendar/Ox.Calendar.js | 25 +- source/Ox.UI/js/Core/Ox.GarbageCollection.js | 3 +- source/Ox.UI/js/Form/Ox.Form.js | 3 +- source/Ox.UI/js/Map/Ox.ListMap.js | 346 ++++++++++++++----- source/Ox.UI/js/Map/Ox.Map.js | 9 +- source/Ox.UI/js/Map/Ox.MapMarker.js | 2 +- source/Ox.UI/js/Video/Ox.AnnotationFolder.js | 5 +- source/Ox.UI/js/Video/Ox.AnnotationPanel.js | 20 +- source/Ox.UI/js/Video/Ox.VideoEditor.js | 234 ++++++------- source/Ox.UI/js/Video/Ox.VideoPlayer.js | 4 + source/Ox.UI/themes/classic/css/classic.css | 14 + source/Ox.UI/themes/modern/css/modern.css | 10 +- source/Ox/js/Format.js | 3 +- source/Ox/js/Function.js | 3 +- 15 files changed, 437 insertions(+), 255 deletions(-) diff --git a/source/Ox.UI/css/Ox.UI.css b/source/Ox.UI/css/Ox.UI.css index c352e0b9..18ac8d32 100644 --- a/source/Ox.UI/css/Ox.UI.css +++ b/source/Ox.UI/css/Ox.UI.css @@ -612,7 +612,8 @@ OxArrayEditable } .OxArrayEditable.OxArrayEditableTextarea .OxEditableElement { padding: 4px; - border-top: 1px solid rgb(128, 128, 128); + border-top-width: 1px; + border-top-style: solid; } .OxArrayEditable.OxArrayEditableTextarea textarea { padding: 4px; @@ -1436,6 +1437,14 @@ Maps height: 16px; border-radius: 8px; } +.OxTypeIcon { + width: 10px; + height: 10px; + border-width: 2px; + border-style: solid; + border-radius: 7px; + margin-left: -3px; +} /* ================================================================================ diff --git a/source/Ox.UI/js/Calendar/Ox.Calendar.js b/source/Ox.UI/js/Calendar/Ox.Calendar.js index 35d6dcd1..990252ee 100644 --- a/source/Ox.UI/js/Calendar/Ox.Calendar.js +++ b/source/Ox.UI/js/Calendar/Ox.Calendar.js @@ -357,7 +357,7 @@ Ox.Calendar = function(options, self) { .addClass('OxCalendarContainer') .css({ top: (self.options.showToolbar * 24) + 16 + 'px', - bottom: (self.options.showZoombar * 16) + 'px' + bottom: (self.options.showZoombar * 16) + 16 + 'px' }) .bind({ mouseleave: mouseleave, @@ -1015,7 +1015,7 @@ Ox.Calendar = function(options, self) { marginLeft: -delta / getScrollbarFactor() + 'px' }, ms); if (!Ox.isUndefined(line)) { - scrollTo((line + 1) * 16 - self.$container.height() / 2, true); + scrollTo(line * 16 + 8 - self.$container.height() / 2, true); } }; @@ -1037,9 +1037,10 @@ Ox.Calendar = function(options, self) { function renderCalendar() { self.contentHeight = Math.max( - self.lineEvents.length * 16 + 16, // fixme: why +16 ? - self.options.height - (self.options.showToolbar * 24) - // self.options.height - 56 // 24 + 16 + 16 + self.lineEvents.length * 16, + self.options.height - 32 + - self.options.showZoombar * 16 + - self.options.showToolbar * 24 ); self.$content.css({height: self.contentHeight + 'px'}); that.$element.find('.OxBackground').empty(); @@ -1117,7 +1118,7 @@ Ox.Calendar = function(options, self) { function scrollBy(delta) { scrollTo( self.$container.$element[0].scrollTop - + delta * self.$container.height() / 2, true + + delta * Math.round(self.$container.height() / 2), true ); } @@ -1125,7 +1126,7 @@ Ox.Calendar = function(options, self) { var containerHeight = self.$container.height(), scrollTop = self.$container.$element[0].scrollTop, min = 0, - max = Math.ceil(self.contentHeight - containerHeight / 2), + max = self.contentHeight - containerHeight, top = Ox.limit(top, min, max), delta = top - scrollTop, ms = 250 * Math.min(Math.abs(delta) / (containerHeight / 2), 1); @@ -1144,14 +1145,11 @@ Ox.Calendar = function(options, self) { if (id) { self.options.selected = id; $element = $element || getEventElementById(id); - if ($element) { - $element.addClass('OxSelected'); - } else { - panToSelected(); - } + $element && $element.addClass('OxSelected'); + panToSelected(); event = Ox.getObjectById(self.options.events, id); - // fixme: map event should also be 'select', not 'selectplace' setEventControls(event); + // fixme: map event should also be 'select', not 'selectplace' that.triggerEvent('select', event); } else { if (self.options.selected !== '') { @@ -1324,7 +1322,6 @@ Ox.Calendar = function(options, self) { }; that.resizeCalendar = function() { - Ox.print('RESIZE CALENDAR') self.options.width = that.width(); self.options.height = that.height(); self.options.showZoombar && self.$zoomInput.options({size: self.options.width}); diff --git a/source/Ox.UI/js/Core/Ox.GarbageCollection.js b/source/Ox.UI/js/Core/Ox.GarbageCollection.js index a6de9772..c96ccac8 100644 --- a/source/Ox.UI/js/Core/Ox.GarbageCollection.js +++ b/source/Ox.UI/js/Core/Ox.GarbageCollection.js @@ -1,4 +1,5 @@ // vim: et:ts=4:sw=4:sts=4:ft=javascript +'use strict'; Ox.GarbageCollection = (function() { @@ -43,4 +44,4 @@ Ox.GarbageCollection = (function() { return that; -}()); \ No newline at end of file +}()); diff --git a/source/Ox.UI/js/Form/Ox.Form.js b/source/Ox.UI/js/Form/Ox.Form.js index f03424c4..0a20f9e3 100644 --- a/source/Ox.UI/js/Form/Ox.Form.js +++ b/source/Ox.UI/js/Form/Ox.Form.js @@ -155,7 +155,8 @@ Ox.Form = function(options, self) { return self.formIsValid; }; - that.values = function() { // fixme: can this be private? + that.values = function() { + // FIXME: this should accept a single string argument to get a single value /* get/set form values call without arguments to get current form values diff --git a/source/Ox.UI/js/Map/Ox.ListMap.js b/source/Ox.UI/js/Map/Ox.ListMap.js index 39fa9b7a..3b7e6369 100644 --- a/source/Ox.UI/js/Map/Ox.ListMap.js +++ b/source/Ox.UI/js/Map/Ox.ListMap.js @@ -10,8 +10,9 @@ Ox.ListMap ListMap object options Options object height Height in px labels If true, show labels + mode Mode ('add' or 'define') places Array of places, or function that returns places - selected Selected places + selected Id of the selected place width Width in px self Shared private variable @*/ @@ -23,9 +24,11 @@ Ox.ListMap = function(options, self) { .defaults({ addPlace: null, editPlace: null, + collapsible: false, getMatches: null, height: 256, labels: false, + mode: 'add', pageLength: 100, places: null, removePlace: null, @@ -37,7 +40,6 @@ Ox.ListMap = function(options, self) { width: 256 }) .options(options || {}) - .addClass('OxListMap') .css({ width: self.options.width + 'px', height: self.options.height + 'px' @@ -45,6 +47,21 @@ Ox.ListMap = function(options, self) { self.isAsync = Ox.isFunction(self.options.places); + // FIXME: duplicated, see MapMarker + self.typeColor = { + country: [64, 64, 255], + region: [0, 192, 192], + city: [255, 0, 0], + borough: [255, 128, 0], + street: [255, 255, 0], + building: [255, 64, 128], + feature: [0, 192, 0] + }; + self.areaSize = { + 10000: 8, // 100 x 100 m + 10000000000: 10 // 100 x 100 km + }; + self.columns = [ { addable: false, // fixme: implement @@ -56,26 +73,78 @@ Ox.ListMap = function(options, self) { }, { format: function(value, data) { - return data.geoname ? $('') - .attr({ - src: Ox.getFlagByGeoname(data.geoname, 16) - }) - .css({ - width: '14px', - height: '14px', - borderRadius: '4px', - marginLeft: '-3px', - marginTop: 0 - }) : ''; + return data.geoname + ? $('') + .attr({ + src: Ox.getFlagByGeoname(data.geoname, 16) + }) + .css({ + width: '14px', + height: '14px', + borderRadius: '4px', + marginLeft: '-3px', + marginTop: 0 + }) + : ''; }, id: 'countryCode', + map: function(value) { + var names = value.split(', '); + if (!Ox.getCountryByGeoname(names[names.length - 1])) { + names.push('~'); + } + return names.reverse().join(', '); + }, + operator: '+', resizable: false, // fixme: implement title: 'Flag', titleImage: 'flag', + tooltip: function(data) { + return Ox.toTitleCase(data.geoname || '') + }, visible: true, width: 16 }, { + format: function(value, data) { + var iconSize = 6; + Ox.forEach(self.areaSize, function(size, area) { + if (data.area >= area) { + iconSize = size; + } else { + return false; + } + }); + return data.type + ? $('
') + .addClass('OxTypeIcon') + .css({ + width: iconSize + 'px', + height: iconSize + 'px', + borderRadius: (iconSize + 4) / 2 + 'px', + margin: [0, 0, 0, -3].map(function(v) { + return v + (10 - iconSize) / 2 + 'px'; + }).join(' '), + background: 'rgb(' + self.typeColor[data.type].join(', ') + ')' + }) + : ''; + }, + id: 'type', + operator: '+', + title: 'Type', + titleImage: 'icon', + tooltip: function(data) { + return Ox.toTitleCase(data.type || ''); + }, + visible: true, + width: 16 + }, + { + format: function(value, data) { + return data.type + ? value + : $('').addClass('OxWarning').html(value) + }, id: 'name', operator: '+', removable: false, @@ -99,21 +168,29 @@ Ox.ListMap = function(options, self) { var names = value.split(', '); if (!Ox.getCountryByGeoname(names[names.length - 1])) { names.push('~'); - } - return names.reverse().join(', '); - }, - operator: '+', - title: 'Geoname', - visible: true, - width: 192 + } + return names.reverse().join(', '); + }, + operator: '+', + title: 'Geoname', + visible: false, + width: 192 }, { - format: function(value) { - return Ox.toTitleCase(value); - }, - id: 'type', + align: 'right', + format: toFixed, + id: 'lat', operator: '+', - title: 'Type', + title: 'Latitude', + visible: true, + width: 64 + }, + { + align: 'right', + format: toFixed, + id: 'lng', + operator: '+', + title: 'Longitude', visible: true, width: 64 }, @@ -124,7 +201,7 @@ Ox.ListMap = function(options, self) { operator: '+', title: 'South', visible: false, - width: 96 + width: 64 }, { align: 'right', @@ -132,7 +209,7 @@ Ox.ListMap = function(options, self) { operator: '+', title: 'West', visible: false, - width: 96 + width: 64 }, { align: 'right', @@ -141,7 +218,7 @@ Ox.ListMap = function(options, self) { operator: '+', title: 'North', visible: false, - width: 96 + width: 64 }, { align: 'right', @@ -150,25 +227,7 @@ Ox.ListMap = function(options, self) { operator: '+', title: 'East', visible: false, - width: 96 - }, - { - align: 'right', - format: toFixed, - id: 'lat', - operator: '+', - title: 'Latitude', - visible: true, - width: 96 - }, - { - align: 'right', - format: toFixed, - id: 'lng', - operator: '+', - title: 'Longitude', - visible: true, - width: 96 + width: 64 }, { align: 'right', @@ -267,6 +326,7 @@ Ox.ListMap = function(options, self) { columnsVisible: true, //items: Ox.clone(self.options.places), items: self.options.places, + keys: ['area', 'geoname'], // needed for icon and flag max: 1, min: 0, pageLength: self.options.pageLength, @@ -353,7 +413,20 @@ Ox.ListMap = function(options, self) { }); self.$placeTitle = $('
') .hide() - .appendTo(self.$placeTitlebar) + .appendTo(self.$placeTitlebar); + if (self.options.mode == 'define') { + self.$findPlaceButton = Ox.Button({ + title: 'find', + tooltip: 'Find', + type: 'image' + }) + .css({float: 'left', margin: '4px'}) + .bindEvent({ + click: findPlace + }) + .hide() + .appendTo(self.$placeTitle); + } self.$placeFlag = $('') .addClass('OxFlag') .attr({ @@ -438,6 +511,7 @@ Ox.ListMap = function(options, self) { country = Ox.getCountryByGeoname(geoname), countryCode = country ? country.code : '', isResult = self.selectedPlace[0] == '_'; + Ox.print('CHANGE', geoname, country, countryCode, self.isAsync); self.$placeFlag.attr({ src: Ox.getFlagByGeoname(geoname, 16) }); @@ -564,9 +638,9 @@ Ox.ListMap = function(options, self) { self.$newPlaceButton = Ox.Button({ title: 'New Place', - width: 96 + width: 70 }) - .css({float: 'left', margin: '4px 2px 4px 4px'}) + .css({float: 'left', margin: '4px'}) .bindEvent({ click: function() { self.$map.newPlace(); @@ -574,14 +648,14 @@ Ox.ListMap = function(options, self) { }) .appendTo(self.$placeStatusbar); - self.$placeButton = Ox.Button({ - title: 'Add Place', - width: 96 + self.$addPlaceButton = Ox.Button({ + tooltip: 'Add Place', + width: 90 }) - .css({float: 'right', margin: '4px 4px 4px 2px'}) + .css({float: 'right', margin: '4px'}) .bindEvent({ click: function() { - if (self.$placeButton.options('title') == 'Add Place') { + if (this.options('title') == 'Add Place') { addPlace(); } else { removePlace(); @@ -591,6 +665,25 @@ Ox.ListMap = function(options, self) { .hide() .appendTo(self.$placeStatusbar); + if (self.options.mode == 'define') { + self.$definePlaceButton = Ox.Button({ + title: 'Define Place', + width: 80 + }) + .css({float: 'right', margin: '4px 0 4px 0'}) + .bindEvent({ + click: function() { + if (this.options('title') == 'Define Place') { + definePlace(); + } else { + clearPlace(); + } + } + }) + .hide() + .appendTo(self.$placeStatusbar); + } + /* self.$revertButton = Ox.Button({ title: 'Revert', @@ -625,7 +718,7 @@ Ox.ListMap = function(options, self) { that.$element = Ox.SplitPanel({ elements: [ { - collapsible: true, + collapsible: self.options.collapsible, element: self.$listPanel = Ox.SplitPanel({ elements: [ { @@ -647,10 +740,15 @@ Ox.ListMap = function(options, self) { size: 256 }, { - element: self.$map, + element: self.$map + .bindEvent({ + resize: function() { + self.$map.resizeMap(); + } + }), }, { - collapsible: true, + collapsible: self.options.collapsible, element: Ox.SplitPanel({ elements: [ { @@ -674,15 +772,19 @@ Ox.ListMap = function(options, self) { self.$placeFormItems.forEach(function($item) { $item.options({width: data.size - 16}); }); + self.$areaKmInput.options({width: data.size - 16}); + self.$matchesInput && self.$matchesInput.options({width: data.size - 16}); } }), resizable: true, - resize: [204, 256, 384], + resize: [256, 384], size: 256 } ], orientation: 'horizontal' - }).$element + }) + .addClass('OxListMap') + .$element ); function addPlace() { @@ -698,19 +800,19 @@ Ox.ListMap = function(options, self) { selected: [place.id] }); self.$map.addPlace(place); - self.$placeButton.options({title: 'Remove Place'}); + self.$addPlaceButton.options({title: 'Remove Place'}); //setStatus(); } //that.triggerEvent('addplace', {place: place}); if (self.isAsync) { - self.$placeButton.options({disabled: true, title: 'Adding Place'}); + self.$addPlaceButton.options({disabled: true, title: 'Adding...'}); self.options.addPlace(place, function(result) { if (result.status.code == 200) { place.id = result.data.id; self.selectedPlace = place.id; self.$list.reloadList().options({selected: [place.id]}); self.$map.addPlace(place); - self.$placeButton.options({disabled: false, title: 'Remove Place'}); + self.$addPlaceButton.options({disabled: false, title: 'Remove Place'}); } else { if (result.data.names) { if (result.data.names.indexOf(self.$nameInput.value()) > -1) { @@ -721,13 +823,37 @@ Ox.ListMap = function(options, self) { if (result.data.geoname) { self.$geonameInput.addClass('OxError'); } - self.$placeButton.options({disabled: false, title: 'Add Place'}); + self.$addPlaceButton.options({disabled: false, title: 'Add Place'}); } }); } } + function clearPlace() { + var values = { + id: self.selectedPlace, + alternativeNames: [], geoname: '', type: '', + latitude: null, longitude: null, + south: null, west: null, north: null, east: null + }; + self.$definePlaceButton.options({disabled: true, title: 'Clearing...'}); + self.options.editPlace(values, function() { + self.$list.reloadList(); + self.$map.removePlace(); + self.$findPlaceButton.show(); + self.$placeFlag.hide(); + self.$placeForm.hide(); + self.$definePlaceButton.options({disabled: false, title: 'Define Place'}) + }); + } + + function definePlace() { + self.$map.newPlace(); + self.$definePlaceButton.options({title: 'Clear Place'}) + } + function editPlace(keys) { + Ox.print('EDIT PLACE', keys, self.$placeForm.values()) var values = Ox.filter(self.$placeForm.values(), function(values, key) { return keys.indexOf(key) > -1; }); @@ -749,6 +875,12 @@ Ox.ListMap = function(options, self) { } } + function findPlace() { + self.$map.options({ + find: self.$list.value(self.selectedPlace).name + }); + } + function initList(data) { self.$status.html( Ox.formatNumber(data.items) + ' Place' + ( @@ -777,52 +909,100 @@ Ox.ListMap = function(options, self) { index = Ox.getIndexById(self.options.places, self.selectedPlace); self.options.places.splice(index, 1); self.$list.options({items: Ox.clone(self.options.places)}); + self.$addPlaceButton.options({title: 'Add Place'}); //setStatus(); } // fixme: what is this? both options.removePlace and event removeplace?? if (self.isAsync) { + self.$addPlaceButton.options({disabled: true, title: 'Removing...'}) self.options.removePlace({id: self.selectedPlace}, function() { self.$list.options({selected: []}).reloadList(true); + self.$addPlaceButton.options({disabled: false, title: 'Add Place'}) }); } self.$map.removePlace(); - self.$placeButton.options({title: 'Add Place'}); that.triggerEvent('removeplace', {id: self.selectedPlace}); } function selectItem(data) { - var id = data.ids.length ? data.ids[0] : null; - self.$map.options({selected: id}); - id && self.$map.panToPlace(); + // Select item in list + var isUndefined, selectedPlace; + self.options.selected = data.ids.length ? data.ids[0] : ''; + isUndefined = !!self.options.selected + && !self.$list.value(self.options.selected, 'type'); + selectedPlace = self.options.selected && !isUndefined + ? self.options.selected : null; + self.$map.options({selected: selectedPlace}); + selectedPlace && self.$map.panToPlace(); + if (isUndefined) { + self.selectedPlace = self.options.selected; + self.$findPlaceButton.show(); + self.$placeFlag.hide(); + self.$placeName.options({ + title: self.$list.value(self.options.selected, 'name') + }); + self.$placeTitle.show(); + self.$definePlaceButton.options({ + title: 'Define Place' + }).show(); + self.$addPlaceButton.options({ + title: 'Remove Place' + }).show(); + } } function selectPlace(place) { - var isResult = place.id && place.id[0] == '_'; - self.$list.options({ - selected: place.id && !isResult ? [place.id] : [] - }); - if (place.id) { - //isResult && self.options.places.push(place); - self.selectedPlace = place.id; - self.$placeFlag.attr({ - src: Ox.getFlagByGeoname(place.geoname, 16) + // Select place on map + Ox.print('selectPlace', place) + var isResult, isUndefined; + self.selectedPlace = place.id || null; + isResult = !!place.id && place.id[0] == '_'; + isUndefined = !!self.options.selected + && !self.$list.value(self.options.selected, 'type'); + Ox.print('isResult', isResult, 'isUndefined', isUndefined, self.options.selected) + if (!isUndefined) { + self.$list.options({ + selected: place.id && !isResult ? [place.id] : [] }); - self.$placeName.options({title: place.geoname || ''}); + } + if (place.id) { + if (isResult && isUndefined) { + place.name = self.$list.value(self.selectedPlace, 'name'); + } + self.options.mode == 'define' && self.$findPlaceButton.hide(); + self.$placeFlag.attr({ + src: Ox.getFlagByGeoname(place.geoname || '', 16) + }).show(); + self.$placeName.options({title: place.name || ''}); self.$placeTitle.show(); - self.$areaKmInput.value(Ox.formatArea(place.area)); self.$placeForm.values(place).show(); - self.$placeButton.options({title: isResult ? 'Add Place' : 'Remove Place'}).show(); + self.$areaKmInput.value(Ox.formatArea(place.area)); updateMatches(); + self.options.mode == 'define' && self.$definePlaceButton.options({ + title: isUndefined && !isResult ? 'Define Place' : 'Clear Place' + }).show(); + self.$addPlaceButton.options({ + title: isResult ? 'Add Place' : 'Remove Place' + }).show(); + if (isResult && isUndefined) { + Ox.print('???? s.o.s s.sP', self.options.selected, self.selectedPlace) + self.selectedPlace = self.options.selected; + editPlace([ + 'geoname', 'type', + 'latitude', 'longitude', + 'south', 'west', 'north', 'east', 'area' + ]); + } } else { - self.selectedPlace = null; self.$placeTitle.hide(); self.$placeForm.hide(); - self.$placeButton.hide(); + self.options.mode == 'define' && self.$definePlaceButton.hide(); + self.$addPlaceButton.hide(); } } function toFixed(val) { - return Ox.isNumber(val) ? val.toFixed(8) : val; // fixme: why can a string be passed ?? + return Ox.isNumber(val) ? val.toFixed(3) : ''; } function toggleList() { diff --git a/source/Ox.UI/js/Map/Ox.Map.js b/source/Ox.UI/js/Map/Ox.Map.js index 3cf7dbe6..8f23a7c7 100644 --- a/source/Ox.UI/js/Map/Ox.Map.js +++ b/source/Ox.UI/js/Map/Ox.Map.js @@ -849,7 +849,8 @@ Ox.Map = function(options, self) { }); if (self.options.find) { - self.$findInput.value(self.options.find) + self.$findInput + .value(self.options.find) .triggerEvent('submit', {value: self.options.find}); } else { if (self.options.selected) { @@ -1388,7 +1389,11 @@ Ox.Map = function(options, self) { } self.setOption = function(key, value) { - if (key == 'height' || key == 'width') { + if (key == 'find') { + self.$findInput + .value(self.options.find) + .triggerEvent('submit', {value: self.options.find}); + } else if (key == 'height' || key == 'width') { that.$element.css(key, value + 'px'); that.resizeMap(); } else if (key == 'places') { diff --git a/source/Ox.UI/js/Map/Ox.MapMarker.js b/source/Ox.UI/js/Map/Ox.MapMarker.js index eb7e7b08..ad3a6053 100644 --- a/source/Ox.UI/js/Map/Ox.MapMarker.js +++ b/source/Ox.UI/js/Map/Ox.MapMarker.js @@ -209,7 +209,7 @@ Ox.MapMarker = function(options) { } that.size = 8; Ox.forEach(areaSize, function(size, area) { - if (that.place.area > area) { + if (that.place.area >= area) { that.size = size; } else { return false; diff --git a/source/Ox.UI/js/Video/Ox.AnnotationFolder.js b/source/Ox.UI/js/Video/Ox.AnnotationFolder.js index ffd606e3..449b67ba 100644 --- a/source/Ox.UI/js/Video/Ox.AnnotationFolder.js +++ b/source/Ox.UI/js/Video/Ox.AnnotationFolder.js @@ -396,9 +396,8 @@ Ox.AnnotationFolder = function(options, self) { } function isDefined(item) { - return self.options.type == 'event' - ? item.event.start !== '' - : item.place.lat !== null; + return !!item[self.options.type] + && !!item[self.options.type].type; } function removeAnnotation(data) { diff --git a/source/Ox.UI/js/Video/Ox.AnnotationPanel.js b/source/Ox.UI/js/Video/Ox.AnnotationPanel.js index 787fc59d..d52cc59b 100644 --- a/source/Ox.UI/js/Video/Ox.AnnotationPanel.js +++ b/source/Ox.UI/js/Video/Ox.AnnotationPanel.js @@ -281,6 +281,7 @@ Ox.AnnotationPanel = function(options, self) { } function selectAnnotation(data, index) { + Ox.print('selectAnnotation', index) self.options.selected = data.id; if (data.id) { self.$folder.forEach(function($folder, i) { @@ -288,25 +289,6 @@ Ox.AnnotationPanel = function(options, self) { }); scrollToSelected(self.options.layers[index].type); } - /* - if (data.top) { - data.bottom = data.top + data.height; - height = self.$folders.height(); - top = self.$folders.offset().top; - scrollTop = self.$folders.scrollTop(); - if (data.top < top || data.bottom > height) { - if (data.top < top) { - Ox.print('top scrollTop', data.top, scrollTop) - scrollTop += data.top - top; - } else { - scrollTop += data.bottom - top - height; - } - self.$folders.animate({ - scrollTop: scrollTop + 'px' - }, 0); - } - } - */ that.triggerEvent('select', data); } diff --git a/source/Ox.UI/js/Video/Ox.VideoEditor.js b/source/Ox.UI/js/Video/Ox.VideoEditor.js index 0175c639..27e5b61a 100644 --- a/source/Ox.UI/js/Video/Ox.VideoEditor.js +++ b/source/Ox.UI/js/Video/Ox.VideoEditor.js @@ -73,6 +73,9 @@ Ox.VideoEditor = function(options, self) { }, key_alt_shift_right: function() { }, + key_b: function() { + selectAnnotation(getNextAnnotation('annotation', -1)); + }, key_backslash: function() { selectAnnotation(); }, @@ -112,7 +115,7 @@ Ox.VideoEditor = function(options, self) { }); }, key_g: function() { - self.results.length && selectAnnotation(getNextResult(1)); + self.results.length && selectAnnotation(getNextAnnotation('result', 1)); }, key_i: function() { setPoint('in', self.options.position); @@ -123,6 +126,9 @@ Ox.VideoEditor = function(options, self) { key_minus: function() { self.options.videoSize == 'large' && toggleSize(); }, + key_n: function() { + selectAnnotation(getNextAnnotation('annotation', 1)); + }, key_o: function() { setPoint('out', self.options.position); }, @@ -146,7 +152,7 @@ Ox.VideoEditor = function(options, self) { movePositionBy(self.options.duration); }, key_shift_g: function() { - self.results.length && selectAnnotation(getNextResult(-1)); + self.results.length && selectAnnotation(getNextAnnotation('result', -1)); }, key_shift_left: function() { movePositionBy(-1); @@ -183,9 +189,12 @@ Ox.VideoEditor = function(options, self) { self.$player = []; self.$timeline = []; + self.annotations = getAnnotations(); self.controlsHeight = 16; self.editing = false; self.margin = 8; + self.positions = getPositions(); + self.results = []; self.words = getWords(); Ox.print('VIDEO EDITOR OPTIONS', self.options) @@ -354,7 +363,7 @@ Ox.VideoEditor = function(options, self) { }); self.$keyboardShortcuts = $('
').css({margin: '16px'}); - [ + Ox.merge([ {key: Ox.UI.symbols.space, action: 'Play/Pause'}, {key: 'P', action: 'Play In to Out'}, {key: '0', action: 'Mute/Unmute'}, @@ -375,17 +384,19 @@ Ox.VideoEditor = function(options, self) { {key: '[', action: 'Go to Previous Annotation'}, {key: ']', action: 'Go to Next Annotation'}, {key: '\\', action: 'Select Current Annotation'}, + {key: 'B', action: 'Select Previous Annotation'}, + {key: 'N', action: 'Select Next Annotation'}, {key: '<', action: 'Go to Previous Cut'}, {key: '>', action: 'Go to Next Cut'}, {key: '/', action: 'Select Current Cut'}, {key: 'F', action: 'Find'}, {key: Ox.UI.symbols.shift + 'G', action: 'Go to Previous Result'}, {key: 'G', action: 'Go to Next Result'}, - {key: 'S', action: 'Select Current Annotation'}, - {key: 'E', action: 'Edit Selected Annotation'}, - {key: Ox.UI.symbols['return'], action: 'Submit'}, - {key: Ox.UI.symbols.escape, action: 'Cancel'} - ].forEach(function(shortcut) { + {key: Ox.UI.symbols['return'], action: 'Edit/Submit'}, + {key: Ox.UI.symbols.escape, action: 'Cancel/Deselect'} + ], self.options.layers.map(function(layer, i) { + return {key: i + 1, action: 'Add ' + layer.item}; + })).forEach(function(shortcut) { self.$keyboardShortcuts.append( $('
').css({display: 'table-row'}) .append( @@ -532,7 +543,7 @@ Ox.VideoEditor = function(options, self) { .css({float: 'right'}) .bindEvent({ click: function() { - selectAnnotation(getNextResult(1)); + selectAnnotation(getNextAnnotation('result', 1)); } }) .appendTo(self.$menubar); @@ -547,7 +558,7 @@ Ox.VideoEditor = function(options, self) { .css({float: 'right'}) .bindEvent({ click: function() { - selectAnnotation(getNextResult(-1)); + selectAnnotation(getNextAnnotation('result', -1)); } }) .appendTo(self.$menubar); @@ -614,17 +625,7 @@ Ox.VideoEditor = function(options, self) { that.triggerEvent('info', data); }, paused: togglePaused, - remove: function(data) { - Ox.print('REMOVE EVENT REACHED EDITOR', data) - var layer = Ox.getObjectById(self.options.layers, data.layer), - index = Ox.getIndexById(layer.items, data.id); - updateWords('remove'); - layer.items.splice(index, 1); - self.editing = false; - self.options.selected = ''; - setTimelineState(); - that.triggerEvent('removeannotation', data); - }, + remove: removeAnnotation, resize: resizeAnnotations, resizeend: resizeendAnnotations, resizecalendar: function(data) { @@ -736,37 +737,17 @@ Ox.VideoEditor = function(options, self) { out: item.out } : null; }); - }))).sort(function(a, b) { - var ret = 0; - if (a['in'] < b['in']) { - ret = -1; - } else if (a['in'] > b['in']) { - ret = 1; - } else if (a.out < b.out) { - ret = -1; - } else if (a.out > b.out) { - ret = 1; - } - return ret; - }); + }))).sort(sortAnnotations); } return results; } function getAnnotation() { // Get annotation at current position - var annotations = []; - self.options.layers.forEach(function(layer) { - layer.items.forEach(function(item) { - if ( - item['in'] <= self.options.position - && item.out >= self.options.position - ) { - annotations.push(item); - } - }); - }); - annotations.sort(function(a, b) { + var annotations = self.annotations.filter(function(annotation) { + return annotation['in'] <= self.options.position + && annotation.out >= self.options.position + }).sort(function(a, b) { var aValue = self.options.position - a['in'], bValue = self.options.position - b['in'], ret = 0; @@ -788,6 +769,12 @@ Ox.VideoEditor = function(options, self) { return annotations.length ? annotations[0] : {id: ''}; } + function getAnnotations() { + return Ox.flatten(Ox.merge(self.options.layers.map(function(layer) { + return layer.items; + }))).sort(sortAnnotations); + } + function getAnnotationValue(annotationId) { var found = false, value; Ox.forEach(self.options.layers, function(layer, i) { @@ -803,26 +790,43 @@ Ox.VideoEditor = function(options, self) { return value; } + function getNextAnnotation(type, direction) { + // type can be 'annotation' or 'result + var annotation, + annotations = type == 'annotation' ? self.annotations : self.results, + index, + position; + if (self.options.selected) { + index = Ox.getIndexById(annotations, self.options.selected); + if (index > -1 && self.options.position == annotations[index]['in']) { + annotation = annotations[Ox.mod(index + direction, annotations.length)]; + } + } + if (!annotation) { + position = getNextPosition(type, direction); + annotations = annotations.filter(function(annotation) { + return annotation['in'] == position; + }); + annotation = annotations[direction == 1 ? 0 : annotations.length - 1]; + } + return annotation; + } + // fixme: why not goToNextPosition()? function getNextPosition(type, direction) { + // type can be 'annotation', 'cut' or 'result' var found = false, position = 0, positions; if (type == 'annotation') { - positions = Ox.sort(Ox.unique(Ox.flatten( - self.options.layers.map(function(layer) { - return layer.items.map(function(item) { - return item['in']; - }); - }) - ))); + positions = self.positions; } else if (type == 'cut') { positions = Ox.merge(0, self.options.cuts, self.options.duration); - }/* else if (type == 'result') { - positions = self.results.map(function(v) { - return v['in']; - }); - }*/ + } else if (type == 'result') { + positions = Ox.unique(self.results.map(function(result) { + return result['in']; + })); + } direction == -1 && positions.reverse(); Ox.forEach(positions, function(v) { if ( @@ -842,69 +846,12 @@ Ox.VideoEditor = function(options, self) { return position; } - function getNextResult(direction) { - var found = false, index, result; - if ( - self.currentResult - && self.options.position == Ox.getObjectById(self.results, self.currentResult)['in'] - ) { - index = Ox.getIndexById(self.results, self.currentResult); - result = self.results[Ox.mod(index + direction, self.results.length)]; - } else { - direction == -1 && self.results.reverse(); - Ox.forEach(self.results, function(v) { - if ( - direction == 1 - ? v['in'] > self.options.position - : v['in'] < self.options.position - ) { - result = v; - found = true; - return false; - } - }); - direction == -1 && self.results.reverse(); - if (!found) { - Ox.print('!found', direction, self.results[direction == 1 ? 0 : results.length - 1]) - result = self.results[direction == 1 ? 0 : self.results.length - 1]; - } - } - self.currentResult = result.id; - return result; + function getPositions() { + return Ox.unique(self.annotations.map(function(annotation) { + return annotation['in']; + })); } - /* - function getPoints(type) { - var found = false, - points, - positions = []; - if (type == 'annotation') { - - } else if (type == 'cut') { - positions = self.options.cuts; - } else if (type == 'result') { - // ... - } else if (type == 'subtitle') { - // FIXME: remove? annotation? - self.options.subtitles.forEach(function(v, i) { - positions.push(v['in']); - positions.push(v.out); - }); - } - positions.indexOf(0) == -1 && positions.unshift(0); - positions.indexOf(self.options.duration) == -1 - && positions.push(self.options.duration); - Ox.forEach(positions, function(v, i) { - if (v > self.options.position) { - points = [positions[i - 1], positions[i]]; - found = true; - return false; - } - }); - return points; - } - */ - function getSizes(scrollbarIsVisible) { var scrollbarWidth = Ox.UI.SCROLLBAR_SIZE, contentWidth = self.options.width @@ -1005,6 +952,20 @@ Ox.VideoEditor = function(options, self) { self.$player[0].playInToOut(); } + function removeAnnotation(data) { + Ox.print('REMOVE EVENT REACHED EDITOR', data) + var layer = Ox.getObjectById(self.options.layers, data.layer), + index = Ox.getIndexById(layer.items, data.id); + updateWords('remove'); + layer.items.splice(index, 1); + self.annotations = getAnnotations(); + self.positions = getPositions(); + self.editing = false; + self.options.selected = ''; + setTimelineState(); + that.triggerEvent('removeannotation', data); + } + function resizeAnnotations(data) { self.options.annotationsSize = data.size; setSizes(); @@ -1137,18 +1098,35 @@ Ox.VideoEditor = function(options, self) { : self.options.selected ? 'selected' : 'default' }); - //Ox.print('SET STATE', self.$timeline[1].options('state')) + } + + function sortAnnotations(a, b) { + var ret = 0; + if (a['in'] < b['in']) { + ret = -1; + } else if (a['in'] > b['in']) { + ret = 1; + } else if (a.out < b.out) { + ret = -1; + } else if (a.out > b.out) { + ret = 1; + } else if (a.value < b.value) { + ret = -1; + } else if (a.value > b.value) { + ret = 1; + } + return ret; } function submitAnnotation(data) { + self.annotations = getAnnotations(); + self.positions = getPositions(); updateWords('add'); self.editing = false; setTimelineState(); - Ox.print('....', self.options.annotationsRange == 'position', - self.options.position < self.options['in'], - self.options.position > self.options.out) if ( - self.options.annotationsRange == 'position' && ( + self.options.annotationsRange == 'position' + && ( self.options.position < self.options['in'] || self.options.position > self.options.out ) @@ -1185,7 +1163,7 @@ Ox.VideoEditor = function(options, self) { if (hasPressedEnter) { that.triggerEvent('find', {find: self.options.find}); if (self.results.length) { - selectAnnotation(getNextResult(1)); + selectAnnotation(getNextAnnotation('result', 1)); } else { self.$findInput.focusInput(true); } @@ -1235,7 +1213,9 @@ Ox.VideoEditor = function(options, self) { } else { self.words[index].count++; } - } else { + } else if (index > -1) { + // index is -1 when removing an annotation by editing + // (which removes the words) and clearing its value if (self.words[index].count == 1) { self.words.splice(index, 1); } else { @@ -1276,7 +1256,7 @@ Ox.VideoEditor = function(options, self) { that.updateAnnotation = function(id, annotation) { // called from editannotation callback - self.options.selected = annotation.id; + self.options.selected = annotation.id; // fixme: needed? self.$annotationPanel.updateItem(id, annotation); } diff --git a/source/Ox.UI/js/Video/Ox.VideoPlayer.js b/source/Ox.UI/js/Video/Ox.VideoPlayer.js index be51ce93..555484db 100644 --- a/source/Ox.UI/js/Video/Ox.VideoPlayer.js +++ b/source/Ox.UI/js/Video/Ox.VideoPlayer.js @@ -692,6 +692,10 @@ Ox.VideoPlayer = function(options, self) { blur: function() { self.inputHasFocus = false; submitPositionInput(); + }, + submit: function() { + self.inputHasFocus = false; + submitPositionInput(); } }) .appendTo(self['$controls' + titleCase].$element); diff --git a/source/Ox.UI/themes/classic/css/classic.css b/source/Ox.UI/themes/classic/css/classic.css index a1945035..c9a64a1a 100644 --- a/source/Ox.UI/themes/classic/css/classic.css +++ b/source/Ox.UI/themes/classic/css/classic.css @@ -362,6 +362,13 @@ Forms background: -webkit-linear-gradient(top, rgb(160, 160, 160), rgb(192, 192, 192)); } +.OxThemeClassic .OxArrayEditable.OxArrayEditableTextarea .OxEditableElement { + border-top-color: rgb(208, 208, 208); +} +.OxThemeClassic .OxEditableElement.OxSelected { + background: rgb(208, 208, 208); +} + /* ================================================================================ Images @@ -521,6 +528,13 @@ Maps color: rgb(64, 64, 64); } +.OxThemeClassic .OxTypeIcon { + border-color: rgb(0, 0, 0); +} +.OxThemeClassic .OxListMap .OxWarning { + border-bottom: 2px dotted rgb(255, 64, 64); +} + /* ================================================================================ Menus diff --git a/source/Ox.UI/themes/modern/css/modern.css b/source/Ox.UI/themes/modern/css/modern.css index 2dacf2aa..53e46dc7 100644 --- a/source/Ox.UI/themes/modern/css/modern.css +++ b/source/Ox.UI/themes/modern/css/modern.css @@ -232,7 +232,6 @@ Forms } */ - .OxThemeModern input.OxCheckbox, .OxThemeModern input.OxInput, .OxThemeModern textarea, @@ -350,6 +349,9 @@ Forms background: -webkit-linear-gradient(top, rgb(0, 0, 0), rgb(32, 32, 32) 10%, rgb(64, 64, 64)); } +.OxThemeModern .OxArrayEditable.OxArrayEditableTextarea .OxEditableElement { + border-top-color: rgb(48, 48, 48); +} .OxThemeModern .OxEditableElement.OxSelected { background: rgb(48, 48, 48); } @@ -518,6 +520,12 @@ Maps background: rgba(0, 0, 0, 0.5); color: rgb(192, 192, 192); } +.OxThemeModern .OxTypeIcon { + border-color: rgb(255, 255, 255); +} +.OxThemeModern .OxListMap .OxWarning { + border-bottom: 2px dotted rgb(192, 0, 0); +} /* ================================================================================ diff --git a/source/Ox/js/Format.js b/source/Ox/js/Format.js index 311bf10f..595eb2ab 100644 --- a/source/Ox/js/Format.js +++ b/source/Ox/js/Format.js @@ -9,9 +9,10 @@ Ox.formatArea Formats a number of meters as square meters or kilometers @*/ Ox.formatArea = function(num, dec) { + dec = Ox.isUndefined(dec) ? 8 : dec; var km = num >= 1000000; return Ox.formatNumber( - (km ? num / 1000000 : num).toPrecision(8) + (km ? num / 1000000 : num).toPrecision(dec) ) + ' ' + (km ? 'k' : '') + 'm\u00B2'; }; diff --git a/source/Ox/js/Function.js b/source/Ox/js/Function.js index c3958401..f59f1c18 100644 --- a/source/Ox/js/Function.js +++ b/source/Ox/js/Function.js @@ -1,3 +1,4 @@ +'use strict'; /*@ Ox.cache Memoize a function