From ae2b1854aab0517e4ab2d58b96e5e8317aca96e3 Mon Sep 17 00:00:00 2001 From: Sanjay Bhangar Date: Sat, 15 Jul 2023 20:43:03 +0530 Subject: [PATCH 01/33] [wip] create LeafletMap using leaflet instead of google maps --- .../maps/world_map_with_countries/index.html | 2 +- .../world_map_with_countries/js/example.js | 2 +- source/UI/js/Map/LeafletMap.js | 1627 +++++++++++++++++ source/UI/js/Map/LeafletMapMarker.js | 335 ++++ source/UI/js/Map/LeafletMapPlace.js | 220 +++ source/UI/js/Map/LeafletMapRectangle.js | 133 ++ source/UI/js/Map/LeafletMapRectangleMarker.js | 126 ++ source/UI/js/Map/Map.js | 1 - 8 files changed, 2443 insertions(+), 3 deletions(-) create mode 100644 source/UI/js/Map/LeafletMap.js create mode 100644 source/UI/js/Map/LeafletMapMarker.js create mode 100644 source/UI/js/Map/LeafletMapPlace.js create mode 100644 source/UI/js/Map/LeafletMapRectangle.js create mode 100644 source/UI/js/Map/LeafletMapRectangleMarker.js diff --git a/examples/maps/world_map_with_countries/index.html b/examples/maps/world_map_with_countries/index.html index b636b69e..fa0fe639 100644 --- a/examples/maps/world_map_with_countries/index.html +++ b/examples/maps/world_map_with_countries/index.html @@ -5,7 +5,7 @@ - + diff --git a/examples/maps/world_map_with_countries/js/example.js b/examples/maps/world_map_with_countries/js/example.js index e93b3295..f4ba0f1b 100644 --- a/examples/maps/world_map_with_countries/js/example.js +++ b/examples/maps/world_map_with_countries/js/example.js @@ -7,7 +7,7 @@ on a world map. Ox.load(['UI', 'Geo'], function() { - var $map = Ox.Map({ + var $map = Ox.LeafletMap({ keys: ['region'], markerColor: function(place) { return Ox.getGeoColor(place.region); diff --git a/source/UI/js/Map/LeafletMap.js b/source/UI/js/Map/LeafletMap.js new file mode 100644 index 00000000..d58a39ba --- /dev/null +++ b/source/UI/js/Map/LeafletMap.js @@ -0,0 +1,1627 @@ +'use strict'; + +/*@ +Ox.Map Basic map object + # DESCRIPTION -------------------------------------------------------------- + `Ox.Map` is a wrapper around the [Google Maps + API](http://code.google.com/apis/maps/documentation/javascript/). + # ARGUMENTS ---------------------------------------------------------------- + options options + clickable If true, clicking on the map finds a place + editable If true, places are editable + find Initial query + findPlaceholder Placeholder text for the find input element + keys Additional place properties to be requested + markerColor <[n]|f|s|'auto'> Color of place markers ([r, g, b]) + markerSize Size of place markers in px + markerTooltip Format function for place marker tooltips + maxMarkers Maximum number of markers to be displayed + places <[o]|f|null> Array of, or function that returns, place objects + countryCode ISO 3166 country code + east Longitude of the eastern boundary in degrees + editable If true, the place is editable + geoname Geoname (like "Paris, Île-de-France, France") + lat Latitude in degrees + lng Longitude in degrees + name Name (like "Paris") + north Latitude of the northern boundary in degrees + south Latitude of the southern boundary in degrees + type Type (like "city" or "country") + west Longitude of the western boundary in degrees + selected Id of the selected place + showControls If true, show controls + showLabels If true, show labels on the map + showStatusbar If true, the map has a statusbar + showToolbar If true, the map has a toolbar + showZoombar If true, the map has a zoombar + zoomOnlyWhenFocused If true, scroll-zoom only when focused + self Shared private variable + # USAGE -------------------------------------------------------------------- + ([options[, self]]) -> Map object + # EVENTS --------------------------------------------------------------- + addplace Fires when a place has been added + place Place object + editplace Fires when a place has been edited + place Place object + geocode Fires when a google geocode request returns + latLng Query coordinates, or undefined + lat latitude + lng longitude + address Query string, or undefined + results <[o]> Google Maps geocode results + address_components <[o]> Address components + long_name Long name + short_name Short name + types <[s]> Types (like "country" or "political") + formatted_address Formatted address + geometry Geometry + bounds Bounds + northEast North-east corner + lat Latitude + lng Longitude + southWest South-west corner + lat Latitude + lng Longitude + location Location + lat Latitude + lng Longitude + location_type Location type (like "APPROXIMATE") + viewport Viewport + northEast North-east corner + lat Latitude + lng Longitude + southWest South-west corner + lat Latitude + lng Longitude + types <[s]> Types (like "country" or "political") + load load + select Fires when a place has been selected or deselected + place Place object + selectplace Deprecated + togglecontrols togglecontrols + togglelabels togglelabels +@*/ + +Ox.LeafletMap = function(options, self) { + + self = self || {}; + var that = Ox.Element({}, self) + .defaults({ + // fixme: isClickable? + clickable: false, + editable: false, + find: '', + findPlaceholder: 'Find', + keys: [], + markerColor: 'auto', + markerSize: 'auto', + markerTooltip: function(place) { + return place.name || 'Unnamed'; + }, + maxMarkers: 100, + places: null, + selected: '', + showControls: false, + showLabels: false, + showStatusbar: false, + showToolbar: false, + showZoombar: false, + zoomOnlyWhenFocused: false + // fixme: width, height + }) + .options(options || {}) + .update({ + find: function() { + self.$findInput + .value(self.options.find) + .triggerEvent('submit', {value: self.options.find}); + }, + height: function() { + that.css({height: self.options.height + 'px'}); + that.resizeMap(); + }, + places: function() { + if (Ox.isArray(self.options.places)) { + self.options.places.forEach(function(place) { + if (Ox.isUndefined(place.id)) { + place.id = Ox.encodeBase32(Ox.uid()); + } + }); + if (self.options.selected && !Ox.getObjectById( + self.options.places, self.options.selected + )) { + self.options.selected = ''; + selectPlace(null); + } + self.options.places = Ox.api(self.options.places, { + geo: true, + sort: '-area' + }); + } + self.loaded && getMapBounds(function(mapBounds) { + if (mapBounds) { + self.map.fitBounds(mapBounds); + } else { + self.map.setZoom(self.minZoom); + self.map.setCenter([0, 0]); + } + // fixme: the following is just a guess + self.boundsChanged = true; + mapChanged(); + self.options.selected && selectPlace(self.options.selected); + }); + }, + selected: function() { + selectPlace(self.options.selected || null); + }, + type: function() { + // ... + }, + width: function() { + that.css({width: self.options.width + 'px'}); + that.resizeMap(); + } + }) + .addClass('OxMap') + .bindEvent({ + gainfocus: function() { + self.options.zoomOnlyWhenFocused && self.map.setOptions({scrollwheel: true}); + }, + losefocus: function() { + self.options.zoomOnlyWhenFocused && self.map.setOptions({scrollwheel: false}); + }, + key_0: function() { + panToPlace() + }, + key_c: toggleControls, + key_down: function() { + pan(0, 1); + }, + key_enter: pressEnter, + key_escape: pressEscape, + key_equal: function() { + zoom(1); + }, + key_l: toggleLabels, + key_left: function() { + pan(-1, 0); + }, + 'key_meta': function() { + self.metaKey = true; + $(document).one({ + keyup: function() { + self.metaKey = false; + } + }); + }, + key_minus: function() { + zoom(-1); + }, + key_right: function() { + pan(1, 0); + }, + key_shift: function() { + self.shiftKey = true; + $(document).one({ + keyup: function() { + self.shiftKey = false; + } + }); + }, + key_shift_down: function() { + pan(0, 2); + }, + key_shift_0: function() { + zoomToPlace(); + }, + key_shift_equal: function() { + zoom(2); + }, + key_shift_left: function() { + pan(-2, 0); + }, + key_shift_minus: function() { + zoom(-2); + }, + key_shift_right: function() { + pan(2, 0); + }, + key_shift_up: function() { + pan(0, -2); + }, + key_up: function() { + pan(0, -1); + }, + key_z: undo, + mousedown: function(e) { + !$(e.target).is('input') && that.gainFocus(); + } + }); + + // HANDLE DEPRECATED OPTIONS + options && ['statusbar', 'toolbar', 'zoombar'].forEach(function(key) { + if (options[key]) { + self.options['show' + Ox.toTitleCase(key)] = options[key]; + } + }); + + //FIXME: duplicated in update + if (Ox.isArray(self.options.places)) { + self.options.places.forEach(function(place) { + if (Ox.isUndefined(place.id)) { + place.id = Ox.encodeBase32(Ox.uid()); + } + }); + self.options.places = Ox.api(self.options.places, { + geo: true, + sort: '-area' + }); + } + + self.mapHeight = getMapHeight(); + self.metaKey = false; + self.minZoom = getMinZoom(); + self.placeKeys = [ + 'id', 'name', 'alternativeNames', 'geoname', 'countryCode', 'type', + 'lat', 'lng', 'south', 'west', 'north', 'east', 'area', + 'editable' + ].concat(self.options.keys); + self.places = [], + self.resultPlace = null; + self.scaleMeters = [ + 50000000, 20000000, 10000000, + 5000000, 2000000, 1000000, + 500000, 200000, 100000, + 50000, 20000, 10000, + 5000, 2000, 1000, + 500, 200, 100, + 50, 20, 10 + ]; + self.shiftKey = false, + self.tileSize = 256; + + if (self.options.showToolbar) { + self.$toolbar = Ox.Bar({ + size: 24 + }) + .appendTo(that); + self.$menu = Ox.MenuButton({ + items: [ + { + id: 'toggleLabels', + title: self.options.showLabels + ? [Ox._('Hide Labels'), Ox._('Show Labels')] + : [Ox._('Show Labels'), Ox._('Hide Labels')], + keyboard: 'l' + }, + { + id: 'toggleControls', + title: self.options.showControls + ? [Ox._('Hide Controls'), Ox._('Show Controls')] + : [Ox._('Show Controls'), Ox._('Hide Controls')], + keyboard: 'c' + } + ], + title: 'set', + tooltip: Ox._('Map Options'), + type: 'image' + }) + .css({float: 'left', margin: '4px'}) + .bindEvent({ + click: function(data) { + if (data.id == 'toggleLabels') { + toggleLabels(); + } else if (data.id == 'toggleControls') { + toggleControls(); + } + } + }) + .appendTo(self.$toolbar); + self.$findInput = Ox.Input({ + clear: true, + placeholder: self.options.findPlaceholder, + width: 192 + }) + .css({float: 'right', margin: '4px 4px 4px 2px'}) + .bindEvent({ + submit: submitFind + }) + .appendTo(self.$toolbar); + self.$loadingIcon = Ox.LoadingIcon({ + size: 16 + }) + .css({float: 'right', margin: '4px 2px 4px 2px'}) + .appendTo(self.$toolbar); + } + + self.$map = Ox.Element() + .css({ + left: 0, + top: self.options.showToolbar * 24 + 'px', + right: 0, + bottom: self.options.showZoombar * 16 + self.options.showStatusbar * 24 + 'px' + }) + .appendTo(that); + + if (self.options.showZoombar) { + self.$zoombar = Ox.Bar({ + size: 16 + }) + .css({ + bottom: self.options.showStatusbar * 24 + 'px' + }) + .appendTo(that); + } + + if (self.options.showStatusbar) { + self.$statusbar = Ox.Bar({ + size: 24 + }) + .css({ + bottom: 0 + }) + .appendTo(that); + self.$placeFlag = Ox.$('') + .addClass('OxFlag') + .attr({ + src: Ox.PATH + 'Ox.Geo/png/icons/16/NTHH.png' + }) + .css({float: 'left', margin: '4px 2px 4px 4px'}) + .appendTo(self.$statusbar); + self.$placeNameInput = Ox.Input({ + placeholder: 'Name', + width: 96 + }) + //.css({position: 'absolute', left: 4, top: 4}) + .css({float: 'left', margin: '4px 2px 4px 2px'}) + .appendTo(self.$statusbar); + self.$placeGeonameInput = Ox.Input({ + placeholder: 'Geoname', + width: 96 + }) + //.css({position: 'absolute', left: 104, top: 4}) + .css({float: 'left', margin: '4px 2px 4px 2px'}) + .appendTo(self.$statusbar); + self.$placeButton = Ox.Button({ + title: 'New Place', + width: 96 + }) + .css({float: 'right', margin: '4px 4px 4px 2px'}) + .bindEvent({ + click: clickPlaceButton + }) + .appendTo(self.$statusbar); + } + + self.$controls = { + center: Ox.Button({ + title: 'center', + type: 'image' + }) + .addClass('OxMapControl OxMapButtonCenter') + .bindEvent({ + singleclick: function() { + panToPlace(); + }, + doubleclick: function() { + zoomToPlace(); + } + }), + east: Ox.Button({ + title: 'right', + type: 'image' + }) + .addClass('OxMapControl OxMapButtonEast') + .bindEvent({ + singleclick: function() { + pan(1, 0); + }, + doubleclick: function() { + pan(2, 0); + } + }), + north: Ox.Button({ + title: 'up', + type: 'image' + }) + .addClass('OxMapControl OxMapButtonNorth') + .bindEvent({ + singleclick: function() { + pan(0, -1); + }, + doubleclick: function() { + pan(0, -2); + } + }), + south: Ox.Button({ + title: 'down', + type: 'image' + }) + .addClass('OxMapControl OxMapButtonSouth') + .bindEvent({ + singleclick: function() { + pan(0, 1); + }, + doubleclick: function() { + pan(0, 2); + } + }), + west: Ox.Button({ + title: 'left', + type: 'image' + }) + .addClass('OxMapControl OxMapButtonWest') + .bindEvent({ + singleclick: function() { + pan(-1, 0); + }, + doubleclick: function() { + pan(-2, 0); + } + }), + scale: Ox.Label({ + textAlign: 'center' + }) + .addClass('OxMapControl OxMapScale') + }; + !self.options.showControls && Ox.forEach(self.$controls, function($control) { + $control.css({opacity: 0}).hide(); + }); + + self.$placeControls = { + flag: Ox.Element() + .addClass('OxPlaceControl OxPlaceFlag') + .bindEvent({ + anyclick: function() { + // FIXME: doesn't work for 'Georgia', use Ox.Geo data! + var country = this.data('country'); + country && getPlaceByName(country, function(place) { + place && self.map.fitBounds(place.bounds); + }); + } + }), + name: Ox.Label({ + textAlign: 'center', + tooltip: Ox._('Click to pan, doubleclick to zoom') + }) + .addClass('OxPlaceControl OxPlaceName') + .bindEvent({ + singleclick: function() { + panToPlace(); + }, + doubleclick: function() { + zoomToPlace(); + } + }), + deselectButton: Ox.Button({ + title: 'close', + tooltip: Ox._('Deselect'), + type: 'image' + }) + .addClass('OxPlaceControl OxPlaceDeselectButton') + .bindEvent({ + click: function() { + selectPlace(null); + } + }) + } + Ox.forEach(self.$placeControls, function($placeControl) { + $placeControl.css({opacity: 0}).hide(); + }); + + if (window.L) { + // timeout needed so that the map is in the DOM + setTimeout(initMap); + } else { + Ox.getStylesheet('https://unpkg.com/leaflet@1.9.4/dist/leaflet.css', () => {}) + + $.getScript( + document.location.protocol + + '//unpkg.com/leaflet@1.9.4/dist/leaflet.js', + function() { + console.log('loaded leaflet'); + console.log(window.L); + initMap(); + } + ); + } + + function addPlaceToMap(place) { + // via find, click, shift-click, or new place button + Ox.Log('Map', 'addPlaceToMap', place) + var exists = false; + if (!place) { + var bounds = self.map.getBounds(), + center = self.map.getCenter(), + southwest = new google.maps.LatLngBounds( + bounds.getSouthWest(), center + ).getCenter(), + northeast = new google.maps.LatLngBounds( + center, bounds.getNorthEast() + ).getCenter(), + place = new Ox.LeafletMapPlace({ + alternativeNames: [], + countryCode: '', + editable: true, + geoname: '', + id: '_' + Ox.encodeBase32(Ox.uid()), // fixme: stupid + map: that, + name: '', + type: 'feature', + south: southwest.lat(), + west: southwest.lng(), + north: northeast.lat(), + east: northeast.lng() + }); + } + Ox.forEach(self.places, function(p, i) { + if (place.bounds.equals(p.bounds)) { + place = p; + exists = true; + return false; // break + } + }); + if (!exists) { + self.resultPlace && self.resultPlace.remove(); + self.resultPlace = place; + place.add(); + } + selectPlace(place.id); + } + + function addPlaceToPlaces(data) { + var place = Ox.extend(getSelectedPlace() || {}, data), + country = Ox.getCountryByGeoname(place.geoname); + place.countryCode = country ? country.code : ''; + self.options.selected = place.id; + setPlaceControls(place); + place.marker.update(); + place.rectangle.update(); + self.places.push(place); + self.resultPlace = null; + that.triggerEvent('addplace', place) + } + + function boundsChanged() { + setScale(); + self.boundsChanged = true; + } + + function canContain(outerBounds, innerBounds) { + // checks if outerBounds _can_ contain innerBounds + var outerSpan = outerBounds.toSpan(), + innerSpan = innerBounds.toSpan(); + return outerSpan.lat() > innerSpan.lat() && + outerSpan.lng() > innerSpan.lng(); + } + + function centerChanged() { + var tooltip = $('.OxMapMarkerTooltip'); + tooltip.length && Ox.$elements[$(tooltip[0]).data('oxid')].hide(); + self.center = self.map.getCenter(); + self.centerChanged = true; + } + + function changeZoom(data) { + self.map.setZoom(data.value); + } + + function clickMap(event) { + var place = getSelectedPlace(); + if (self.options.clickable/* && !editing()*/) { + getPlaceByLatLng(event.latLng, self.map.getBounds(), function(place) { + if (place) { + addPlaceToMap(place); + //selectPlace(place.id); + } else { + selectPlace(null); + } + }); + } else { + pressEscape(); + } + } + + function clickPlaceButton() { + var place = getSelectedPlace(), + title = self.$placeButton.options('title'); + if (title == Ox._('New Place')) { + addPlaceToMap(); + } else if (title == Ox._('Add Place')) { + addPlaceToPlaces(); + } + } + + function constructZoomInput() { + if (self.options.showZoombar) { + self.$zoomInput && self.$zoomInput.remove(); + self.$zoomInput = Ox.Range({ + arrows: true, + changeOnDrag: true, + max: self.maxZoom, + min: self.minZoom, + size: that.width(), + thumbSize: 32, + thumbValue: true, + value: self.map.getZoom() + }) + .bindEvent({ + change: changeZoom + }) + .appendTo(self.$zoombar); + } + } + + function crossesDateline() { + var bounds = self.map.getBounds(); + return bounds.getSouthWest().lng() > bounds.getNorthEast().lng(); + } + + function editing() { + var place = getSelectedPlace(); + return place && place.editing; + } + + function getElevation(point, callback) { + // fixme: unused + if (arguments.length == 1) { + callback = point; + point = self.map.getCenter(); + } + self.elevationService.getElevationForLocations({ + locations: [point] + }, function(data) { + callback(data.elevation); + }); + } + + function getMapBounds(callback) { + // get initial map bounds + self.options.places({}, function(result) { + var area = result.data.area; + console.log('area', area); + console.log(L.latLngBounds( + [area.south, area.west], + [area.north, area.east] + )); + callback(new L.latLngBounds( + [area.south, area.west], + [area.north, area.east] + )); + }); + } + + function getMapHeight() { + return self.options.height + - self.options.showStatusbar * 24 + - self.options.showToolbar * 24 + - self.options.showZoombar * 16; + } + + function getMapType() { + return self.options.showLabels ? 'HYBRID' : 'SATELLITE' + } + + function getMaxZoom(point, callback) { + if (arguments.length == 1) { + callback = point; + point = self.map.getCenter(); + } + setTimeout(function() { + callback(18) + }); + // self.maxZoomService.getMaxZoomAtLatLng(point, function(data) { + // callback(data.status == 'OK' ? data.zoom : null); + // }); + } + + function getMetersPerPixel() { + // m/px = m/deg * deg/px + var degreesPerPixel = 360 / (self.tileSize * Math.pow(2, self.map.getZoom())); + return Ox.getMetersPerDegree(self.map.getCenter().lat) * degreesPerPixel; + } + + function getMinZoom() { + return self.mapHeight > 1024 ? 3 + : self.mapHeight > 512 ? 2 + : self.mapHeight > 256 ? 1 + : 0; + // fixme: there must be a function for this... + /* + return Math.ceil( + Ox.log(self.mapHeight / self.tileSize, 2) + ); + */ + } + + function getPlaceById(id) { + return self.resultPlace && self.resultPlace.id == id + ? self.resultPlace + : Ox.getObjectById(self.places, id); + } + + function getPlaceByLatLng(latlng, bounds, callback) { + // gets the largest place at latlng that would fit in bounds + var callback = arguments.length == 3 ? callback : bounds, + bounds = arguments.length == 3 ? bounds : null; + self.$loadingIcon && self.$loadingIcon.start(); + self.geocoder.geocode({ + latLng: latlng + }, function(results, status) { + self.$loadingIcon && self.$loadingIcon.stop(); + if (status == google.maps.GeocoderStatus.OK) { + if (bounds) { + Ox.forEach(results.reverse(), function(result, i) { + if ( + i == results.length - 1 || + canContain(bounds, result.geometry.bounds || result.geometry.viewport) + ) { + callback(new Ox.LeafletMapPlace(parseGeodata(results[i]))); + return false; // break + } + }); + } else { + callback(new Ox.LeafletMapPlace(parseGeodata(results[0]))); + } + } + if ( + status == google.maps.GeocoderStatus.OK || + status == google.maps.GeocoderStatus.ZERO_RESULTS + ) { + triggerGeocodeEvent({ + latLng: latlng, + results: results + }); + } else { + Ox.Log('Map', 'geocode failed:', status); + callback(null); + } + }); + } + + function getPlaceByName(name, callback) { + self.$loadingIcon && self.$loadingIcon.start(); + self.geocoder.geocode({ + address: name + }, function(results, status) { + self.$loadingIcon && self.$loadingIcon.stop(); + if (status == google.maps.GeocoderStatus.OK) { + callback(new Ox.LeafletMapPlace(parseGeodata(results[0]))); + } + if ( + status == google.maps.GeocoderStatus.OK + && status != google.maps.GeocoderStatus.ZERO_RESULTS + ) { + triggerGeocodeEvent({ + address: name, + results: results + }); + } else { + Ox.Log('Map', 'geocode failed:', status); + callback(null); + } + }); + } + + function getPositionByName(name) { + var position = -1; + Ox.forEach(self.options.places, function(place, i) { + if (place.name == name) { + position = i; + return false; // break + } + }); + return position; + } + + function getSelectedMarker() { + // needed in case self.options.selected + // is changed from outside + var id = null; + if (self.resultPlace && self.resultPlace.selected) { + id = self.resultPlace.id; + } else { + Ox.forEach(self.places, function(place) { + if (place.selected) { + id = place.id; + return false; // break + } + }); + } + return id; + } + + function getSelectedPlace() { + return self.options.selected + ? getPlaceById(self.options.selected) + : null; + } + + function initMap() { + getMapBounds(function(mapBounds) { + console.log('map bounds', mapBounds); + //Ox.Log('Map', 'init', mapBounds.getSouthWest(), mapBounds.getNorthEast(), mapBounds.getCenter()) + + // self.elevationService = new google.maps.ElevationService(); + // self.geocoder = new google.maps.Geocoder(); + // self.maxZoomService = new google.maps.MaxZoomService(); + + self.center = mapBounds ? mapBounds.getCenter() : new L.LatLng([0, 0]); + self.zoom = self.minZoom; + that.map = self.map = new L.Map(self.$map[0], { + center: self.center, + // disableDefaultUI: true, + // disableDoubleClickZoom: true, + // mapTypeId: google.maps.MapTypeId[getMapType()], + // noClear: true, + // scrollwheel: !self.options.zoomOnlyWhenFocused, + zoom: self.zoom + }); + console.log('that map', that.map); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(that.map); + self.map.fitBounds(mapBounds); + self.map.on('moveend', boundsChanged); + self.map.on('moveend', centerChanged); + self.map.on('click', clickMap); + self.map.on('zoomend', zoomChanged); + setTimeout(function() { + mapChanged(); + }); + // google.maps.event.addListener(self.map, 'bounds_changed', boundsChanged); + // google.maps.event.addListener(self.map, 'center_changed', centerChanged); + // google.maps.event.addListener(self.map, 'click', clickMap); + // google.maps.event.addListener(self.map, 'idle', mapChanged); + // google.maps.event.addListener(self.map, 'zoom_changed', zoomChanged); + // google.maps.event.trigger(self.map, 'resize'); + + // needed to get mouse x/y coordinates on marker mouseover, + // see http://code.google.com/p/gmaps-api-issues/issues/detail?id=2342 + + // What does this OverlayView stuff do? + // that.overlayView = new google.maps.OverlayView(); + // that.overlayView.setMap(self.map); + // that.overlayView.draw = function () { + // if (!this.ready) { + // this.ready = true; + // google.maps.event.trigger(this, 'ready'); + // } + // } + // that.overlayView.draw(); + + Ox.forEach(self.$controls, function($control) { + $control.appendTo(self.$map); + }); + Ox.forEach(self.$placeControls, function($placeControl) { + $placeControl.appendTo(self.$map); + }); + + if (self.options.find) { + self.$findInput + .value(self.options.find) + .triggerEvent('submit', {value: self.options.find}); + } else { + if (self.options.selected) { + selectPlace(self.options.selected, true); + } + if (mapBounds) { + if (isEmpty(mapBounds)) { + self.map.setZoom(self.minZoom); + } else { + self.map.fitBounds(mapBounds); + } + } + if (self.map.getZoom() < self.minZoom) { + self.map.setZoom(self.minZoom); + } + } + updateFormElements(); + + self.loaded = true; + that.triggerEvent('load'); + + }); + + } + + function isEmpty(bounds) { + // Google's bounds.isEmpty() is not reliable + var southWest = bounds.getSouthWest(), + northEast = bounds.getNorthEast(); + return southWest.lat == northEast.lat + && southWest.lng == northEast.lng; + } + + function isLoaded() { + return window.google && window.google.maps && window.google.maps.LatLng; + } + + function mapChanged() { + // This is the handler that actually adds the places to the map. + // Gets called after panning or zooming, and when the map is idle. + if (self.boundsChanged) { + var bounds = self.map.getBounds() + if (!bounds) { + self.boundsChanged = false; + return + } + var southWest = bounds.getSouthWest(), + northEast = bounds.getNorthEast(), + south = southWest.lat, + west = southWest.lng, + north = northEast.lat, + east = northEast.lng; + self.options.places({ + keys: self.placeKeys, + query: { + conditions: [].concat([ + {key: 'lat', value: [south, north], operator: '='} + ], spansGlobe() ? [ + {key: 'lng', value: [-180, 180], operator: '='} + ] : crossesDateline() ? [ + {key: 'lng', value: [east, west], operator: '!='} + ] : [ + {key: 'lng', value: [west, east], operator: '='} + ]), + operator: '&' + }, + range: [0, self.options.maxMarkers], + sort: [{key: 'area', operator: '-'}] + }, function(result) { + var ids = self.options.selected ? [self.options.selected] : [], + previousIds = self.places.map(function(place) { + return place.id; + }); + // add new places + result.data.items.forEach(function(item, i) { + var place = getPlaceById(item.id); + if (!place) { + place = new Ox.LeafletMapPlace(Ox.extend({ + map: that + }, item)).add(); + self.places.push(place); + } else if (!place.visible) { + place.add(); + } + item.id != self.options.selected && ids.push(item.id); + }); + // remove old places + previousIds.forEach(function(id) { + var place = getPlaceById(id); + if (place && ids.indexOf(id) == -1) { + place.remove(); + } + }); + // update places array + self.places = self.places.filter(function(place) { + return place.visible; + }); + }); + self.boundsChanged = false; + } + if (self.centerChanged) { + getMaxZoom(function(zoom) { + if (zoom && zoom != self.maxZoom) { + self.maxZoom = zoom; + if (self.map.getZoom() > zoom) { + self.map.setZoom(zoom); + } + constructZoomInput(); + } + }); + self.centerChanged = false; + } + if (self.zoomChanged) { + self.zoomChanged = false; + } + } + + function pan(x, y) { + self.map.panBy(x * self.$map.width() / 2, y * self.$map.height() / 2); + }; + + function panToPlace() { + var place; + if (!self.loaded) { + setTimeout(function() { + panToPlace(); + }, 100); + } else { + place = getSelectedPlace(); + place && self.map.panTo(place.center); + } + } + + function parseGeodata(data) { + var bounds = data.geometry.bounds || data.geometry.viewport, + northEast = bounds.getNorthEast(), + southWest = bounds.getSouthWest(), + place = { + alternativeNames: [], + components: data.address_components, + countryCode: getCountryCode(data.address_components), + east: northEast.lng(), + editable: self.options.editable, + fullGeoname: getFullGeoname(data.address_components), + id: '_' + Ox.encodeBase32(Ox.uid()), + map: that, + north: northEast.lat(), + south: southWest.lat(), + type: getType(data.address_components[0].types), + west: southWest.lng() + }; + place.geoname = data.formatted_address || place.fullGeoname; + place.name = (place.geoname || place.fullGeoname).split(', ')[0]; + if (Math.abs(place.west) == 180 && Math.abs(place.east) == 180) { + place.west = -179.99999999; + place.east = 179.99999999; + } + place.south = Ox.limit(place.south, Ox.MIN_LATITUDE, Ox.MAX_LATITUDE - 0.00000001); + place.north = Ox.limit(place.north, Ox.MIN_LATITUDE + 0.00000001, Ox.MAX_LATITUDE); + function getCountryCode(components) { + var countryCode = ''; + Ox.forEach(components, function(component) { + if (component.types.indexOf('country') > -1) { + countryCode = component.short_name; + return false; // break + } + }); + return countryCode; + } + function getFullGeoname(components) { + var country = false; + return components.map(function(component, i) { + var name = component.long_name; + if (i && components[i - 1].types.indexOf('country') > -1) { + country = true; + } + return !country && ( + i == 0 || name != components[i - 1].long_name + ) ? name : null; + }).join(', '); + } + function getType(types) { + // see https://developers.google.com/maps/documentation/javascript/geocoding#GeocodingAddressTypes + var strings = { + 'country': ['country'], + 'region': ['administrative_area', 'colloquial_area'], + 'city': ['locality'], + 'borough': ['neighborhood', 'postal_code', 'sublocality'], + 'street': [ + 'intersection', 'route', + 'street_address', 'street_number' + ], + 'building': [ + 'airport', 'floor', 'premise', 'room', 'subpremise' + ], + 'feature': ['natural_feature', 'park'] + }, + type; + function find(type) { + var ret; + Ox.forEach(types, function(v) { + ret = Ox.startsWith(v, type); + if (ret) { + return false; // break + } + }); + return ret; + } + Ox.forEach(strings, function(values, key) { + Ox.forEach(values, function(value) { + if (find(value)) { + type = key; + return false; // break + } + }); + if (type) { + return false; // break + } + }); + return type || 'feature'; + } + return place; + } + + function pressEnter() { + var place = getSelectedPlace(); + if (place) { + if (place.editing) { + place.submit(); + } else { + place.edit(); + } + } else if (self.resultPlace) { + selectPlace(self.resultPlace.id) + } + } + + function pressEscape() { + var place = getSelectedPlace(); + if (place) { + if (place.editing) { + place.cancel(); + } else { + selectPlace(null); + } + } else if (self.resultPlace) { + self.resultPlace.remove(); + self.resultPlace = null; + } + } + + // fixme: removePlacefromMap? + function removePlace() { + var place = getSelectedPlace(); + place.id = '_' + place.id; + self.options.selected = place.id; + self.places.splice(Ox.getIndexById(self.places, place.id), 1); + self.resultPlace && self.resultPlace.remove(); + self.resultPlace = place; + place.marker.update(); + place.rectangle.update(); + } + + function reset() { + self.map.getZoom() == self.zoom + ? self.map.panTo(self.center) + : self.map.fitBounds(self.bounds); + } + + function selectPlace(id, zoom) { + // id can be null (deselect) + var place, selected; + if (!self.loaded) { + setTimeout(function() { + selectPlace(id, zoom); + }, 100); + } else { + selected = getSelectedMarker(); + Ox.Log('Map', 'Ox.Map selectPlace()', id, selected); + if (id != selected) { + place = getPlaceById(selected); + place && place.deselect(); + if (id !== null) { + place = getPlaceById(id); + if (place) { + select(); + } else { + // async && place doesn't exist yet + self.options.places({ + keys: self.placeKeys, + query: { + conditions: [ + {key: 'id', value: id, operator: '=='} + ], + operator: '&' + } + }, function(result) { + if (result.data.items.length) { + place = new Ox.LeafletMapPlace(Ox.extend({ + map: that + }, result.data.items[0])).add(); + self.places.push(place); + select(); + if (zoom) { + zoomToPlace(); + } else { + panToPlace(); + } + } + }); + } + } else { + place = null; + select(); + } + } + } + function select() { + place && place.select(); + self.options.selected = id; + setPlaceControls(place); + setStatus(); + that.triggerEvent('selectplace', place); // FIXME: deprecated, remove + that.triggerEvent('select', place); + } + }; + + function setPlaceControls(place) { + var $placeControls = that.find('.OxPlaceControl'), + country, + isVisible = self.$placeControls.name.is(':visible'); + if (place) { + country = place.geoname.indexOf(', ') > -1 + ? place.geoname.split(', ').pop() + : ''; + self.$placeControls.flag.options({ + tooltip: country ? 'Zoom to ' + country : '' + }) + .data({country: country}) + .empty() + .append( + Ox.$('').attr({ + src: Ox.getFlagByGeoname(place.geoname, 16) + }) + ) + .show(); + self.$placeControls.name.options({ + title: place.name ||'Unnamed' + }); + !isVisible && $placeControls.show().animate({opacity: 1}, 250); + } else { + isVisible && $placeControls.animate({opacity: 0}, 250, function() { + $placeControls.hide(); + }); + } + } + + function setScale() { + var metersPerPixel = getMetersPerPixel(); + Ox.forEach(self.scaleMeters, function(meters) { + var mapWidth = self.options.width || that.width(), + scaleWidth = Math.round(meters / metersPerPixel); + if (scaleWidth <= mapWidth / 2 - 4) { + self.$controls.scale + .options({ + title: '\u2190 ' + ( + meters > 1000 ? Ox.formatNumber(meters / 1000) + ' k' : meters + ' ' + ) + 'm \u2192' + }) + .css({ + width: (scaleWidth - 16) + 'px' + }); + return false; // break + } + }); + } + + function setStatus() { + //Ox.Log('Map', 'setStatus()', self.options.selected) + var code, country, disabled, place, title; + if (self.options.showStatusbar) { + place = getSelectedPlace(); + country = place ? Ox.getCountryByGeoname(place.geoname) : ''; + code = country ? country.code : 'NTHH'; + disabled = place && !place.editable; + if (place) { + title = place.id[0] == '_' ? Ox._('Add Place') : Ox._('Remove Place'); + } else { + title = Ox._('New Place'); + } + self.$placeFlag.attr({ + src: Ox.PATH + 'Ox.Geo/png/icons/16/' + code + '.png' + }); + self.$placeNameInput.options({ + disabled: disabled, + value: place ? place.name : '' + }); + self.$placeGeonameInput.options({ + disabled: disabled, + value: place ? place.geoname : '' + }); + self.$placeButton.options({ + disabled: disabled, + title: title + }); + } + //Ox.Log('Map', 'STATUS DONE'); + } + + function spansGlobe() { + // fixme: or self.options.width ?? + return self.$map.width() > self.tileSize * Math.pow(2, self.map.getZoom()); + }; + + function submitFind(data) { + self.options.find = data.value; + if (data.value === '') { + if (self.options.selected && self.options.selected[0] == '_') { + selectPlace(null); + } + } else { + that.findPlace(data.value, function(place) { + setStatus(place); + }); + } + that.triggerEvent('find', {value: data.value}); + } + + function toggleControls() { + var $controls = that.find('.OxMapControl'); + self.options.showControls = !self.options.showControls; + if (self.options.showControls) { + $controls.show().animate({opacity: 1}, 250); + } else { + $controls.animate({opacity: 0}, 250, function() { + $controls.hide(); + }); + } + that.triggerEvent('togglecontrols', { + visible: self.options.showControls + }); + } + + function toggleLabels() { + self.options.showLabels = !self.options.showLabels; + self.map.setMapTypeId(google.maps.MapTypeId[getMapType()]); + that.triggerEvent('togglelabels', { + visible: self.options.showLabels + }); + } + + function triggerGeocodeEvent(data) { + // someone may want to cache google geocode data, so we fire an event. + // google puts functions like lat or lng on the objects' prototypes, + // so we create properly named properties, for json encoding + if (data.latLng) { + data.latLng = { + lat: data.latLng.lat(), + lng: data.latLng.lng() + } + } + data.results.forEach(function(result) { + ['bounds', 'viewport'].forEach(function(key) { + if (result.geometry[key]) { + result.geometry[key] = { + northEast: { + lat: result.geometry[key].getNorthEast().lat(), + lng: result.geometry[key].getNorthEast().lng() + }, + southWest: { + lat: result.geometry[key].getSouthWest().lat(), + lng: result.geometry[key].getSouthWest().lng() + } + } + } + }); + if (result.geometry.location) { + result.geometry.location = { + lat: result.geometry.location.lat(), + lng: result.geometry.location.lng() + } + } + }); + that.triggerEvent('geocode', data); + } + + function undo() { + Ox.Log('Map', 'Map undo') + var place = getSelectedPlace(); + place.editing && place.undo(); + } + + function updateFormElements() { + var width = that.width(); + if (self.options.showZoombar) { + getMaxZoom(function(zoom) { + self.maxZoom = zoom; + constructZoomInput(); + }); + } + if (self.options.showStatusbar) { + self.$placeNameInput.options({ + width: Math.floor((width - 132) / 2) + }); + self.$placeGeonameInput.options({ + width: Math.ceil((width - 132) / 2) + }); + } + } + + function zoom(z) { + if (!self.loaded) { + setTimeout(function() { + zoom(z); + }, 100); + } else { + self.map.setZoom(self.map.getZoom() + z); + } + } + + function zoomChanged() { + var zoom = self.map.getZoom(); + if (zoom < self.minZoom) { + self.map.setZoom(self.minZoom); + } else if (self.maxZoom && zoom > self.maxZoom) { + self.map.setZoom(self.maxZoom); + } else { + self.zoomChanged = true; + self.$zoomInput && self.$zoomInput.value(zoom); + that.triggerEvent('zoom', { + value: zoom + }); + } + } + + function zoomToPlace() { + var place; + if (!self.loaded) { + setTimeout(function() { + zoomToPlace(); + }, 100); + } else { + place = getSelectedPlace(); + place && self.map.fitBounds(place.bounds); + } + } + + /*@ + addPlace addPlace + (data) -> add place to places + @*/ + that.addPlace = function(data) { + addPlaceToPlaces(data); + }; + + /*@ + getCenter Returns the map center + () -> Map center + lat Latitude + lng Longitude + @*/ + that.getCenter = function() { + var center = self.map.getCenter(); + return {lat: center.lat(), lng: center.lng()}; + }; + + /*@ + getKey getKey + () -> get key + @*/ + that.getKey = function() { + return self.shiftKey ? 'shift' + : self.metaKey ? 'meta' + : null; + }; + + /*@ + getSelectedPlace getSelectedPlace + () -> get selected place + @*/ + that.getSelectedPlace = function() { + return getSelectedPlace(); + } + + /*@ + editPlace editPlace + () -> edit selected place + @*/ + that.editPlace = function() { + getSelectedPlace().edit(); + return that; + }; + + /*@ + findPlace findPlace + (name, callback) -> find place and pass to callback + @*/ + that.findPlace = function(name, callback) { + getPlaceByName(name, function(place) { + if (place) { + addPlaceToMap(place); + self.map.fitBounds(place.bounds); + } else { + name && self.$findInput.addClass('OxError'); + } + callback(place); + }); + return that; + }; + + /*@ + newPlace newPlace + (place) -> add place to map + @*/ + that.newPlace = function(place) { + addPlaceToMap(place); + return that; + }; + + /*@ + panToPlace panToPlace + () -> pan to place + @*/ + that.panToPlace = function() { + panToPlace(); + return that; + }; + + /*@ + removePlace removePlace + () -> remove selected place from places + @*/ + that.removePlace = function() { + // fixme: removePlaceFromPlaces() ? + removePlace(); + return that; + }; + + /*@ + resizeMap resizeMap + () -> resize map + @*/ + that.resizeMap = function() { + // keep center on resize has been commented out + // var center = self.map.getCenter(); + self.options.height = that.height(); + self.options.width = that.width(); + // check if map has initialized + if (self.map) { + self.mapHeight = getMapHeight(); + self.minZoom = getMinZoom(); + if (self.minZoom > self.map.getZoom()) { + self.map.setZoom(self.minZoom); + } + self.$map.css({ + height: self.mapHeight + 'px', + width: self.options.width + 'px' + }); + self.options.$zoomInput && self.$zoomInput.options({ + size: self.options.width + }); + updateFormElements(); + Ox.Log('Map', 'triggering google maps resize event, height', self.options.height) + google.maps.event.trigger(self.map, 'resize'); + // self.map.setCenter(center); + } + return that; + } + + /*@ + setCenter Set map center + (center) -> Map + center Map center + lat Latitude + lng Longitude + @*/ + that.setCenter = function(center) { + self.map.setCenter(new google.maps.LatLng(center.lat, center.lng)); + return that; + }; + + /*@ + value value + (id, key, value) -> set id, key to value + @*/ + that.value = function(id, key, value) { + // fixme: should be like the corresponding List/TableList/etc value function + Ox.Log('Map', 'Map.value', id, key, value); + getPlaceById(id).options(key, value); + if (id == self.options.selected) { + if (key == 'name') { + self.$placeControls.name.options({title: value}); + } else if (key == 'geoname') { + self.$placeControls.flag.empty().append( + Ox.$('').attr({ + src: Ox.getFlagByGeoname(value, 16) + }) + ); + } + } + return that; + } + + /*@ + zoomToPlace zoomToPlace + () -> zoom to selected place + @*/ + that.zoomToPlace = function() { + zoomToPlace(); + return that; + }; + + /*@ + zoom zoom + (value) -> zoom to value + @*/ + that.zoom = function(value) { + zoom(value); + return that; + }; + + return that; + +}; diff --git a/source/UI/js/Map/LeafletMapMarker.js b/source/UI/js/Map/LeafletMapMarker.js new file mode 100644 index 00000000..69495da8 --- /dev/null +++ b/source/UI/js/Map/LeafletMapMarker.js @@ -0,0 +1,335 @@ +'use strict'; + +/*@ +Ox.MapMarker MapMarker + (options) -> MapMarker object + options Options object + color marker color + map map + place place + size size +@*/ + +Ox.LeafletMapMarker = function(options) { + + options = Ox.extend({ + map: null, + place: null + }, options); + + var that = this, + areaSize = { + 100: 10, // 10 x 10 m + 10000: 12, // 100 x 100 m + 1000000: 14, // 1 x 1 km + 100000000: 16, // 10 x 10 km + 10000000000: 18, // 100 x 100 km + 1000000000000: 20, // 1,000 x 1,000 km + 100000000000000: 22 // 10,000 x 10,000 km + }, + themeData = Ox.Theme.getThemeData(), + typeColor = {}; + + [ + 'country', 'region', 'city', 'borough', + 'street', 'building', 'feature' + ].forEach(function(type) { + typeColor[type] = themeData[ + 'mapPlace' + Ox.toTitleCase(type) + 'Color' + ]; + }); + + Ox.forEach(options, function(val, key) { + that[key] = val; + }); + console.log('place', options.place); + that.marker = new L.Marker([options.place.center.lat, options.place.center.lng], { + // raiseOnDrag: false, + // shape: {coords: [8, 8, 8], type: 'circle'} + //title: that.place.name, + //zIndex: 1000 + }).addTo(options.map.map); + + // setOptions(); + + function click() { + var key = that.map.getKey(), + place, bounds, southWest, northEast; + if (!that.place.selected) { + if ( + that.map.options('editable') + && (key == 'meta' || key == 'shift') + ) { + place = that.map.getSelectedPlace(); + } + if (place) { + bounds = new L.LatLngBounds( + new L.LatLng(place.south, place.west), + new L.LatLng(place.north, place.east) + ); + // bounds = bounds.union(that.place.bounds); + southWest = bounds.getSouthWest(); + northEast = bounds.getNorthEast(); + that.map.newPlace(new Ox.LeafletMapPlace({ + // fixme: duplicated, see Ox.Map.js + alternativeNames: [], + countryCode: '', + editable: true, + geoname: '', + id: '_' + Ox.encodeBase32(Ox.uid()), // fixme: stupid + map: that.map, + name: '', + type: 'feature', + south: southWest.lat, + west: southWest.lng, + north: northEast.lat, + east: northEast.lng + })); + } else { + console.log('place', that.place); + that.map.options({selected: that.place.id}); + } + } else { + if (key == 'meta') { + that.map.options({selected: null}); + } else { + that.map.panToPlace(); + } + } + } + + function dblclick() { + that.place.selected && that.map.zoomToPlace(); + } + + function dragstart(e) { + Ox.$body.addClass('OxDragging'); + } + + function drag(e) { + var northSouth = (that.place.north - that.place.south) / 2, + lat = Ox.limit( + e.latLng.lat(), + Ox.MIN_LATITUDE + northSouth, + Ox.MAX_LATITUDE - northSouth + ), + lng = e.latLng.lng(), + span = Math.min( + that.place.sizeEastWest * Ox.getDegreesPerMeter(lat) / 2, 179.99999999 + ), + degreesPerMeter = Ox.getDegreesPerMeter(lat); + that.place.south += lat - that.place.lat; + that.place.north += lat - that.place.lat; + that.place.west = lng - span; + that.place.east = lng + span; + if (that.place.west < -180) { + that.place.west += 360; + } else if (that.place.east > 180) { + that.place.east -= 360; + } + Ox.Log('Map', 'west', that.place.west, 'east', that.place.east, 'span', span); + that.place.update(); + that.marker.setOptions({ + position: that.place.center + }); + that.place.rectangle.update(); + } + + function dragend(e) { + Ox.$body.removeClass('OxDragging'); + that.map.triggerEvent('changeplaceend', that.place); + } + + function getMarkerImage(options, callback) { + // fixme: unused + options = Ox.extend({ + background: [255, 0, 0], + editing: false, + result: false, + selected: false, + size: 16 + }, options); + var background = options.result ? [255, 255, 0] : [255, 0, 0], + border = options.editing ? [128, 128, 255] : + options.selected ? [255, 255, 255] : [0, 0, 0], + c = Ox.canvas(options.size, options.size), + image, + r = options.size / 2; + if (Ox.isArray(background)) { + c.context.fillStyle = 'rgba(' + background.join(', ') + ', 0.5)'; + c.context.arc(r, r, r - 2, 0, 360); + c.context.fill(); + renderImage(); + } else { + image = new Image(); + image.onload = renderImage; + image.src = background; + } + function renderImage() { + //var i; + if (Ox.isString(background)) { + c.context.drawImage(image, 1, 1, options.size - 2, options.size - 2); + /* + c.imageData = c.context.getImageData(0, 0, options.size, options.size); + c.data = c.imageData.data; + for (i = 3; i < c.data.length; i += 1) { + c.data[i] = Math.round(c.data[i] * 0.5); + } + c.context.putImageData(c.imageData, 0, 0); + */ + } + c.context.beginPath(); + c.context.lineWidth = 2; + c.context.strokeStyle = 'rgb(' + border.join(', ') + ')'; + c.context.arc(r, r, r - 1, 0, 360); + c.context.stroke(); + callback(new google.maps.MarkerImage( + c.canvas.toDataURL(), + new google.maps.Size(options.size, options.size), + new google.maps.Point(0, 0), + new google.maps.Point(r, r) + )); + } + } + + function mouseover(e) { + var offset = that.map.offset(), + xy = that.map.overlayView.getProjection() + .fromLatLngToContainerPixel(e.latLng); + that.tooltip.show( + offset.left + Math.round(xy.x) - 4, + offset.top + Math.round(xy.y) + 20 + ); + } + + function mouseout() { + that.tooltip.hide(); + } + + function setOptions() { + // workaround to prevent marker from appearing twice + // after setting draggable from true to false (google maps bug) + // var fix = that.marker.getDraggable() && !that.place.editing, + // color = that.map.options('markerColor'), + // size = that.map.options('markerSize'); + var fix = false, + color = that.map.options('markerColor'), + size = that.map.options('markerSize'); + //Ox.Log('Map', 'setOptions, that.map: ', that.map) + if (color == 'auto') { + that.color = typeColor[that.place.type] || typeColor['mapPlaceFeatureColor']; + } else if (Ox.isArray(color)) { + that.color = color; + } else { + that.color = color(that.place); + } + if (size == 'auto') { + that.size = 8; + Ox.forEach(areaSize, function(size, area) { + if (that.place.area >= area) { + that.size = size; + } else { + return false; // break + } + }); + } else if (Ox.isNumber(size)) { + that.size = size; + } else { + that.size = size(that.place); + } + // that.marker.setOptions({ + // // fixme: cursor remains pointer + // cursor: that.place.editing ? 'move' : 'pointer', + // draggable: that.place.editing, + // // icon: Ox.MapMarkerImage({ + // // color: that.color, + // // mode: that.place.editing ? 'editing' : + // // that.place.selected ? 'selected' : 'normal', + // // size: that.size, + // // type: that.place.id[0] == '_' ? 'result' : 'place' + // // }), + // position: that.place.center + // }); + if (fix) { + that.marker.setVisible(false); + setTimeout(function() { + that.marker.setVisible(true); + }, 0); + } + setTooltip(); + } + + function setTooltip() { + that.tooltip && that.tooltip.remove(); + that.tooltip = Ox.Tooltip({ + title: '' + + '
' + + that.map.options('markerTooltip')(that.place) + '
' + }) + .addClass('OxMapMarkerTooltip'); + } + + /*@ + add add to map + () -> add to map, returns MapMarker + @*/ + that.add = function() { + that.marker.addTo(that.map.map); + that.marker.on('click', click); + that.marker.on('dblclick', dblclick); + // that.marker.on('mouseover', mouseover); + // that.marker.on('mouseout', mouseout); + // google.maps.event.addListener(that.marker, 'click', click); + // google.maps.event.addListener(that.marker, 'dblclick', dblclick); + // google.maps.event.addListener(that.marker, 'mouseover', mouseover); + // google.maps.event.addListener(that.marker, 'mouseout', mouseout); + return that; + }; + + /*@ + edit edit marker + () -> edit marker, returns MapMarker + @*/ + that.edit = function() { + setOptions(); + google.maps.event.addListener(that.marker, 'dragstart', dragstart); + google.maps.event.addListener(that.marker, 'drag', drag); + google.maps.event.addListener(that.marker, 'dragend', dragend); + return that; + }; + + /*@ + remove remove marker + () -> remove marker from map, returns MapMarker + @*/ + that.remove = function() { + that.marker.setMap(null); + google.maps.event.clearListeners(that.marker); + return that; + }; + + /*@ + submit submit marker + () -> clear edit listeners, returns MapMarker + @*/ + that.submit = function() { + google.maps.event.clearListeners(that.marker, 'dragstart'); + google.maps.event.clearListeners(that.marker, 'drag'); + google.maps.event.clearListeners(that.marker, 'dragend'); + return that; + } + + /*@ + update update marker + () -> update marker, returns MapMarker + @*/ + that.update = function() { + setOptions(); + return that; + } + + return that; + +}; diff --git a/source/UI/js/Map/LeafletMapPlace.js b/source/UI/js/Map/LeafletMapPlace.js new file mode 100644 index 00000000..e7cdd7bc --- /dev/null +++ b/source/UI/js/Map/LeafletMapPlace.js @@ -0,0 +1,220 @@ +'use strict'; + +/*@ +Ox.MapPlace MapPlace Object + (options) -> MapPlace Object + options Options object + east + editing + geoname + map + markerColor 0> 0]> + markerSize + name + north + selected + south + type + visible + west +@*/ + +Ox.LeafletMapPlace = function(options) { + + options = Ox.extend({ + east: 0, + editing: false, + geoname: '', + map: null, + name: '', + north: 0, + selected: false, + south: 0, + type: '', + visible: false, + west: 0 + }, options); + + var that = this; + + Ox.forEach(options, function(val, key) { + that[key] = val; + }); + + update(); + + function update(updateMarker) { + // console.log('foo', that.north, that.east, that.south, that.west); + that.points = { + ne: new L.LatLng(that.north, that.east), + sw: new L.LatLng(that.south, that.west) + }; + console.log('points', JSON.stringify(that.points, null, 2)); + that.bounds = new L.latLngBounds(that.points.sw, that.points.ne); + that.center = that.bounds.getCenter(); + that.lat = that.center.lat; + that.lng = that.center.lng; + Ox.extend(that.points, { + e: new L.LatLng(that.lat, that.east), + s: new L.LatLng(that.south, that.lng), + se: new L.LatLng(that.south, that.east), + n: new L.LatLng(that.north, that.lng), + nw: new L.LatLng(that.north, that.west), + w: new L.LatLng(that.lat, that.west) + }); + // fixme: use bounds.toSpan() + that.sizeNorthSouth = (that.north - that.south) + * Ox.EARTH_CIRCUMFERENCE / 360; + that.sizeEastWest = (that.east + (that.west > that.east ? 360 : 0) - that.west) + * Ox.getMetersPerDegree(that.lat); + that.area = Ox.getArea( + {lat: that.south, lng: that.west}, + {lat: that.north, lng: that.east} + ); + if (!that.marker) { + that.marker = new Ox.LeafletMapMarker({ + map: that.map, + place: that + }); + that.rectangle = new Ox.LeafletMapRectangle({ + map: that.map, + place: that + }); + } else if (updateMarker) { + that.marker.update(); + that.rectangle.update(); + } + } + + function editable() { + return that.map.options('editable') && that.editable; + } + + /*@ + add add + @*/ + that.add = function() { + that.visible = true; + that.marker.add(); + return that; + }; + + /*@ + cancel cancel + @*/ + that.cancel = function() { + if (editable()) { + that.undo(); + that.editing = false; + that.marker.update(); + that.rectangle.deselect(); + } + return that; + }; + + /*@ + crossesDateline crossesDateline + @*/ + that.crossesDateline = function() { + return that.west > that.east; + } + + /*@ + deselect dselect + @*/ + that.deselect = function() { + that.editing && that.submit(); + that.selected = false; + that.marker.update(); + that.rectangle.remove(); + return that; + }; + + /*@ + edit edit + @*/ + that.edit = function() { + if (editable()) { + that.editing = true; + that.original = { + south: that.south, + west: that.west, + north: that.north, + east: that.east + }; + that.marker.edit(); + that.rectangle.select(); + } + return that; + }; + + // fixme: make this an Ox.Element to get options handling for free? + that.options = function(options) { + options = Ox.makeObject(arguments); + Ox.forEach(options, function(value, key) { + that[key] = value; + }); + update(true); + }; + + /*@ + remove remove + @*/ + that.remove = function() { + that.editing && that.submit(); + that.selected && that.deselect(); + that.visible = false; + that.marker.remove(); + return that; + }; + + /*@ + select select + @*/ + that.select = function() { + that.selected = true; + !that.visible && that.add(); + that.marker.update(); + that.rectangle.add(); + return that; + }; + + /*@ + submit submit + @*/ + that.submit = function() { + if (editable()) { + that.editing = false; + that.marker.update(); + that.rectangle.deselect(); + } + return that; + }; + + /*@ + update update + @*/ + that.update = function(updateMarker) { + update(updateMarker); + that.map.triggerEvent('changeplace', that); + return that; + }; + + /*@ + undo undo + @*/ + that.undo = function() { + if (editable()) { + Ox.forEach(that.original, function(v, k) { + that[k] = v; + }); + that.update(); + that.marker.update(); + that.rectangle.update(); + } + return that; + }; + + return that; + +}; diff --git a/source/UI/js/Map/LeafletMapRectangle.js b/source/UI/js/Map/LeafletMapRectangle.js new file mode 100644 index 00000000..99f1a19e --- /dev/null +++ b/source/UI/js/Map/LeafletMapRectangle.js @@ -0,0 +1,133 @@ +'use strict'; + +/*@ +Ox.MapRectangle MapRectangle Object + (options) -> MapRectangle Object + options Options object + map map + place place +@*/ + +Ox.LeafletMapRectangle = function(options) { + + options = Ox.extend({ + map: null, + place: null + }, options); + + var that = this, + themeData = Ox.Theme.getThemeData(); + + Ox.forEach(options, function(val, key) { + that[key] = val; + }); + + /*@ + rectangle google.maps.Rectangle + @*/ + console.log('place bounds', that.place.bounds); + that.rectangle = new L.rectangle(that.place.bounds); + + /*@ + markers array of markers + @*/ + that.markers = Ox.map(that.place.points, function(point, position) { + return new Ox.LeafletMapRectangleMarker({ + map: that.map, + place: that.place, + position: position + }); + }); + + setOptions(); + + function click() { + if ( + that.map.options('editable') + && that.place.editable + && !that.place.editing + ) { + that.place.edit(); + } else if (that.map.getKey() == 'meta') { + that.place.submit(); + } else if (that.map.getKey() == 'shift') { + that.map.zoomToPlace(); + } else { + that.map.panToPlace(); + } + } + + function setOptions() { + var color = '#' + Ox.toHex(themeData[ + that.place.editing + ? 'mapPlaceEditingBorder' + : 'mapPlaceSelectedBorder' + ]); + // that.rectangle.setOptions({ + // bounds: that.place.bounds, + // fillColor: color, + // fillOpacity: that.place.editing ? 0.1 : 0, + // strokeColor: color, + // strokeOpacity: that.place.id[0] == '_' ? 0.5 : 1, + // strokeWeight: 2 + // }); + } + + /*@ + add add + @*/ + that.add = function() { + that.rectangle.addTo(that.map.map); + that.rectangle.on('click', click); + // google.maps.event.addListener(that.rectangle, 'click', click); + return that; + }; + + /*@ + deselect deselect + @*/ + that.deselect = function() { + setOptions(); + Ox.Log('Map', 'MARKERS', that.markers) + Ox.forEach(that.markers, function(marker) { + marker.remove(); + }); + return that; + }; + + /*@ + remove remove + @*/ + that.remove = function() { + that.rectangle.remove(); + // that.rectangle.setMap(null); + // google.maps.event.clearListeners(that.rectangle); + return that; + } + + /*@ + select select + @*/ + that.select = function() { + setOptions(); + Ox.forEach(that.markers, function(marker) { + marker.add(); + }); + return that; + }; + + /*@ + update udpate + @*/ + that.update = function() { + Ox.Log('Map', 'UPDATE...') + setOptions(); + Ox.forEach(that.markers, function(marker) { + marker.update(); + }); + return that; + } + + return that; + +}; diff --git a/source/UI/js/Map/LeafletMapRectangleMarker.js b/source/UI/js/Map/LeafletMapRectangleMarker.js new file mode 100644 index 00000000..71a0e72c --- /dev/null +++ b/source/UI/js/Map/LeafletMapRectangleMarker.js @@ -0,0 +1,126 @@ +'use strict'; + +/*@ +Ox.MapRectangleMarker MapRectangleMarker Object + (options) -> MapRectangleMarker Object + options Options object + map map + place place + position +@*/ + +Ox.LeafletMapRectangleMarker = function(options) { + + options = Ox.extend({ + map: null, + place: null, + position: '' + }, options); + + var that = this; + + Ox.forEach(options, function(val, key) { + that[key] = val; + }); + console.log('position', that.place.points, that.position); + // that.markerImage = new google.maps.MarkerImage + that.marker = new L.Marker({ + + }); + // that.marker = new google.maps.Marker({ + // cursor: that.position + '-resize', + // draggable: true, + // icon: Ox.MapMarkerImage({ + // mode: 'editing', + // rectangle: true, + // type: that.place.id[0] == '_' ? 'result' : 'place' + // }), + // position: that.place.points[that.position], + // raiseOnDrag: false + // }); + + function dragstart(e) { + Ox.$body.addClass('OxDragging'); + that.drag = { + lat: e.latLng.lat(), + lng: e.latLng.lng() + }; + } + + function drag(e) { + // fixme: implement shift+drag (center stays the same) + Ox.Log('Map', e.pixel.x, e.pixel.y) + var lat = Ox.limit(e.latLng.lat(), Ox.MIN_LATITUDE, Ox.MAX_LATITUDE), + lng = e.latLng.lng(); + that.drag = { + lat: lat, + lng: lng + }; + if (that.position.indexOf('s') > -1) { + that.place.south = lat; + } + if (that.position.indexOf('n') > -1) { + that.place.north = lat; + } + if (that.position.indexOf('w') > -1) { + that.place.west = lng; + } + if (that.position.indexOf('e') > -1) { + that.place.east = lng; + } + //Ox.Log('Map', 'west', that.place.west, 'east', that.place.east); + //Ox.Log('Map', 'south', that.place.south, 'north', that.place.north); + that.place.update(); + that.place.marker.update(); + that.place.rectangle.update(); + } + + function dragend(e) { + var south; + Ox.$body.removeClass('OxDragging'); + if (that.place.south > that.place.north) { + south = that.place.south; + that.place.south = that.place.north; + that.place.north = south; + that.place.update(); + that.place.marker.update(); + that.place.rectangle.update(); + } + that.map.triggerEvent('changeplaceend', that.place); + } + + /*@ + add add + @*/ + that.add = function() { + that.marker.setMap(that.map.map); + google.maps.event.addListener(that.marker, 'dragstart', dragstart); + google.maps.event.addListener(that.marker, 'drag', drag); + google.maps.event.addListener(that.marker, 'dragend', dragend); + }; + + /*@ + remove remove + @*/ + that.remove = function() { + that.marker.setMap(null); + google.maps.event.clearListeners(that.marker); + }; + + /*@ + update update + @*/ + that.update = function() { + that.marker.setOptions({ + icon: Ox.MapMarkerImage({ + mode: 'editing', + rectangle: true, + type: that.place.id[0] == '_' ? 'result' : 'place' + }), + position: that.place.points[that.position] + }); + }; + + return that; + +}; diff --git a/source/UI/js/Map/Map.js b/source/UI/js/Map/Map.js index 9f5c4392..f12e1654 100644 --- a/source/UI/js/Map/Map.js +++ b/source/UI/js/Map/Map.js @@ -832,7 +832,6 @@ Ox.Map = function(options, self) { } function initMap() { - getMapBounds(function(mapBounds) { //Ox.Log('Map', 'init', mapBounds.getSouthWest(), mapBounds.getNorthEast(), mapBounds.getCenter()) From 6e966dee6ceba839947185de8862834d22b55dcf Mon Sep 17 00:00:00 2001 From: j Date: Fri, 21 Jul 2023 11:18:44 +0100 Subject: [PATCH 02/33] union->extend --- source/UI/js/Map/LeafletMapMarker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/UI/js/Map/LeafletMapMarker.js b/source/UI/js/Map/LeafletMapMarker.js index 69495da8..96fb8803 100644 --- a/source/UI/js/Map/LeafletMapMarker.js +++ b/source/UI/js/Map/LeafletMapMarker.js @@ -67,7 +67,7 @@ Ox.LeafletMapMarker = function(options) { new L.LatLng(place.south, place.west), new L.LatLng(place.north, place.east) ); - // bounds = bounds.union(that.place.bounds); + bounds = bounds.extend(that.place.bounds); southWest = bounds.getSouthWest(); northEast = bounds.getNorthEast(); that.map.newPlace(new Ox.LeafletMapPlace({ From bdb416ac113268605cd276578114ea2ebe9d12d6 Mon Sep 17 00:00:00 2001 From: j Date: Fri, 21 Jul 2023 11:20:41 +0100 Subject: [PATCH 03/33] fix places spanning the Antimeridian --- source/UI/js/Map/LeafletMapPlace.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/UI/js/Map/LeafletMapPlace.js b/source/UI/js/Map/LeafletMapPlace.js index e7cdd7bc..a413afa8 100644 --- a/source/UI/js/Map/LeafletMapPlace.js +++ b/source/UI/js/Map/LeafletMapPlace.js @@ -45,11 +45,21 @@ Ox.LeafletMapPlace = function(options) { function update(updateMarker) { // console.log('foo', that.north, that.east, that.south, that.west); + if (that.east < that.west) { + if (that.west > 170) { + that.west -= 360 + //console.log(that.geoname, '<< west', that.west+360, '=>', that.west) + } else { + that.east += 360 + //console.log(that.geoname, '>> east', that.east-360, that.east) + } + } that.points = { ne: new L.LatLng(that.north, that.east), sw: new L.LatLng(that.south, that.west) }; console.log('points', JSON.stringify(that.points, null, 2)); + that.bounds = new L.latLngBounds(that.points.sw, that.points.ne); that.center = that.bounds.getCenter(); that.lat = that.center.lat; From f8b218ec5910ea26cce3f9f5edecf26e36181fe4 Mon Sep 17 00:00:00 2001 From: j Date: Fri, 21 Jul 2023 11:21:30 +0100 Subject: [PATCH 04/33] only post if target exists --- source/UI/js/Core/Event.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/UI/js/Core/Event.js b/source/UI/js/Core/Event.js index 8df1650a..31c1fee8 100644 --- a/source/UI/js/Core/Event.js +++ b/source/UI/js/Core/Event.js @@ -438,7 +438,7 @@ Ox.forEach( Ox.makeObject(Ox.slice(arguments)), function(data, event) { - target.postMessage(JSON.stringify({ + target && target.postMessage(JSON.stringify({ data: data, event: event, target: isParent ? Ox.oxid : null From 99ae1509cdb3604dd556898de0ecb31707ab51a4 Mon Sep 17 00:00:00 2001 From: j Date: Thu, 27 Jul 2023 16:54:38 +0200 Subject: [PATCH 05/33] some turkish translations --- source/Ox/js/Constants.js | 3 +- source/Ox/json/locale.tr.json | 89 +++++++++++ source/UI/json/locale.tr.json | 281 ++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 source/Ox/json/locale.tr.json create mode 100644 source/UI/json/locale.tr.json diff --git a/source/Ox/js/Constants.js b/source/Ox/js/Constants.js index 8f88ca13..1fe2d45b 100644 --- a/source/Ox/js/Constants.js +++ b/source/Ox/js/Constants.js @@ -63,7 +63,8 @@ Ox.LOCALE_NAMES = { 'el': 'Ελληνικά', 'en': 'English', 'fr': 'Français', - 'hi': 'हिन्दी' + 'hi': 'हिन्दी', + 'tr': 'Türkçe' }; //@ Ox.LOCALES Locales per module Ox.LOCALES = {}; diff --git a/source/Ox/json/locale.tr.json b/source/Ox/json/locale.tr.json new file mode 100644 index 00000000..db589319 --- /dev/null +++ b/source/Ox/json/locale.tr.json @@ -0,0 +1,89 @@ +{ + "%": "%", + ",": ",", + ".": ".", + "%A, %B %e, %Y": "%A, %B %e, %Y", + "%a, %b %e, %Y": "%a, %b %e, %Y", + "AD": "", + "AM": "", + "Apr": "", + "April": "Nisan", + "Aug": "", + "August": "Ağustos", + "BC": "", + "%B %e, %Y": "", + "%b %e, %Y": "", + "d": "", + "day": "gün", + "days": "gün", + "days{2}": "gün", + "Dec": "", + "December": "Aralık", + "Fall": "Düşmek", + "Feb": "", + "February": "Şubat", + "Fri": "", + "Friday": "Cuma", + "h": "", + "hour": "saat", + "hours": "saat", + "hours{2}": "saat", + "%I:%M %p": "", + "%I:%M:%S %p": "", + "Jan": "", + "January": "Ocak", + "Jul": "", + "July": "Temmuz", + "Jun": "", + "June": "Haziran", + "m": "", + "Mar": "", + "March": "Mart", + "May": "Mayıs", + "%m/%d/%Y": "", + "%m/%d/%y": "", + "minute": "dakika", + "minutes": "dakika", + "minutes{2}": "dakika", + "Mon": "", + "Monday": "Pazartesi", + "nd": "", + "nd{22}": "", + "no": "", + "Nov": "", + "November": "Kasım", + "Oct": "", + "October": "Ekim", + "PM": "", + "rd": "", + "rd{23}": "", + "s": "", + "Sat": "", + "Saturday": "Cumartesi", + "second": "saniye", + "seconds": "saniye", + "seconds{2}": "saniye", + "Sep": "", + "September": "Eylül", + "Spring": "Bahar", + "st": "", + "st{21}": "", + "Summer": Yaz"", + "Sun": "", + "Sunday": "Pazar", + "th": "", + "th{11}": "", + "th{12}": "", + "th{13}": "", + "Thu": "", + "Thursday": "Perşembe", + "Tue": "", + "Tuesday": "", + "Wed": "", + "Wednesday": "Çarşamba", + "Winter": "kış", + "y": "", + "year": "yıl", + "years": "yıl", + "years{2}": "yıl" +} diff --git a/source/UI/json/locale.tr.json b/source/UI/json/locale.tr.json new file mode 100644 index 00000000..f5f82983 --- /dev/null +++ b/source/UI/json/locale.tr.json @@ -0,0 +1,281 @@ +{ + ", doubleclick to edit": ", düzenlemek için çift tıkla", + "Add": "Ekle", + "Add Files": "Dosya Ekle", + "Add Place": "Yer Ekle", + "Add a condition": "koşul ekle", + "Add a group of conditions": "", + "Add column after": "Sonrasına sütun ekle", + "Add column before": "", + "Add row above": "", + "Add row below": "", + "Add {0}": "", + "Adding...": "", + "All": "Tüm", + "Alternative Names": "", + "Area": "", + "At Current Position": "", + "Blockquote": "", + "Bold": "", + "Borough": "", + "Building": "", + "Bullets": "", + "By Duration": "", + "By Position": "", + "By Text": "", + "Cancel": "", + "Cancel/Deselect": "", + "Cancelled": "", + "City": "", + "Clear": "", + "Clear Event": "", + "Clear Place": "", + "Clearing...": "", + "Click to hide": "", + "Click to pan, doubleclick to zoom": "", + "Click to select": "", + "Click to select, doubleclick to edit": "", + "Click to show": "", + "Close": "", + "Complete": "", + "Country": "", + "Date": "", + "Date Created": "", + "Date Modified": "", + "Define": "", + "Define Event": "", + "Define Place": "", + "Delete Annotation": "", + "Deselect": "", + "Deselect Annotation": "", + "Don't Shuffle": "", + "Done": "", + "Download": "", + "Download Selection...": "", + "Download Video...": "", + "Drag to resize": "", + "Drag to resize or click to hide": "", + "Drag to resize or click to toggle map": "", + "Duration": "", + "East": "", + "Edit": "Düzenlemek", + "Edit Annotation": "", + "Edit/Submit": "", + "Editing Options": "", + "Embed Selection...": "", + "End": "", + "Enter Fullscreen": "", + "Event": "", + "Events": "", + "Examples...": "", + "Exit Fullscreen": "", + "Feature": "", + "Find": "", + "Find in All {0}": "", + "Find in List": "", + "Find in This {0}": "", + "Find on Map": "", + "Find...": "", + "Find: All": "", + "Find: Alternative Names": "", + "Find: Geoname": "", + "Find: Name": "", + "Flag": "", + "Font Size": "", + "Generating Documentation...": "", + "Geoname": "", + "Go One Frame Back": "", + "Go One Frame Forward": "", + "Go One Line Down": "", + "Go One Line Up": "", + "Go One Second Back": "", + "Go One Second Forward": "", + "Go to First Frame": "", + "Go to In Point": "", + "Go to Last Frame": "", + "Go to Next Annotation": "", + "Go to Next Cut": "", + "Go to Next Result": "", + "Go to Out Point": "", + "Go to Poster Frame": "", + "Go to Previous Annotation": "", + "Go to Previous Cut": "", + "Go to Previous Result": "", + "Headline": "", + "Hide": "", + "Hide Controls": "", + "Hide Labels": "", + "Home": "", + "Home Channel": "", + "Image": "", + "Import Annotations...": "", + "In Current Selection": "", + "Insert": "", + "Insert HTML": "", + "Insert...": "", + "Italic": "", + "Join Clip(s) at Cuts": "", + "Keyboard Shortcuts": "", + "Keyboard Shortcuts...": "", + "Large": "", + "Large Player": "", + "Larger": "", + "Latitude": "", + "Limit to": "", + "Linebreak": "", + "Link": "", + "List": "", + "Longitude": "", + "Make Clip(s) Static": "", + "Map Options": "", + "Match": "", + "Matches": "", + "Medium": "", + "Monospace": "", + "Mute": "", + "Mute/Unmute": "", + "Name": "", + "New Event": "", + "New Place": "", + "Next": "", + "Next Channel": "", + "Next Result": "", + "No file selected": "", + "No files selected": "", + "North": "", + "Numbers": "", + "Open in New Tab": "", + "Options": "", + "Other": "", + "Paragraph": "", + "Pause": "", + "Paused": "", + "Person": "", + "Place": "", + "Place or Event": "", + "Play": "", + "Play Current Track": "", + "Play In to Out": "", + "Play Next Track": "", + "Play/Pause": "", + "Previous": "", + "Previous Channel": "", + "Previous Result": "", + "Region": "", + "Reload": "", + "Remove": "", + "Remove Event": "", + "Remove File": "", + "Remove Place": "", + "Remove this column": "", + "Remove this condition": "", + "Remove this group of conditions": "", + "Remove this row": "", + "Removing...": "", + "Repeat All": "", + "Repeat None": "", + "Repeat One": "", + "Reset this condition": "", + "Resolution": "", + "Restart": "", + "Restore Defaults": "", + "Results": "", + "Resume": "", + "Right-to-Left": "", + "Run Tests": "", + "Save Changes": "", + "Save as Smart List": "", + "Scale to Fill": "", + "Scale to Fit": "", + "Scroll to Player": "", + "Select Current Annotation": "", + "Select Current Cut": "", + "Select File": "", + "Select Next Annotation": "", + "Select Previous Annotation": "", + "Set ": "", + "Set In Point": "", + "Set Out Point": "", + "Set Poster Frame": "", + "Settings": "", + "Show Annotations": "", + "Show Controls": "", + "Show Dates": "", + "Show Labels": "", + "Show Other": "", + "Show People": "", + "Show Places": "", + "Show Remaining Time": "", + "Show Subtitles": "", + "Show Users": "", + "Shuffle": "", + "Small": "", + "Small Player": "", + "Smaller": "", + "Sort Annotations": "", + "South": "", + "Split Clip(s) at Cuts": "", + "Start": "", + "Street": "", + "Strike": "", + "Subscript": "", + "Subtitles": "", + "Superscript": "", + "Switch Theme": "", + "Timeline": "", + "Title": "", + "Turn Volume Down": "", + "Turn Volume Up": "", + "Type": "", + "Underline": "", + "Undo Changes": "", + "Unmute": "", + "Untitled": "", + "User": "", + "Valid": "", + "View": "", + "View Live": "", + "View Source": "", + "View as Grid": "", + "View as List": "", + "Volume": "", + "West": "", + "add": "", + "all": "", + "and": "", + "annotations": "", + "any": "", + "ascending": "", + "bracket": "", + "contains": "", + "descending": "", + "does not contain": "", + "does not end with": "", + "does not start with": "", + "ends with": "", + "file": "", + "files": "", + "in": "", + "is": "", + "is after": "", + "is before": "", + "is between": "", + "is greater than": "", + "is less than": "", + "is not": "", + "is not after": "", + "is not before": "", + "is not between": "", + "is not greater than": "", + "is not less than": "", + "items": "", + "of the following conditions": "", + "order": "", + "sorted by": "", + "starts with": "", + "unknown": "", + "{0} Century": "", + "{0} Century BC": "", + "{0} Millennium": "", + "{0} Millennium BC": "" +} From 7ffb5b6b9e24b2fd479bb30fb0a1960613fe7b76 Mon Sep 17 00:00:00 2001 From: j Date: Fri, 4 Aug 2023 14:58:37 +0200 Subject: [PATCH 06/33] typo --- source/Ox/json/locale.tr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/Ox/json/locale.tr.json b/source/Ox/json/locale.tr.json index db589319..2a9b1f0b 100644 --- a/source/Ox/json/locale.tr.json +++ b/source/Ox/json/locale.tr.json @@ -68,7 +68,7 @@ "Spring": "Bahar", "st": "", "st{21}": "", - "Summer": Yaz"", + "Summer": "Yaz", "Sun": "", "Sunday": "Pazar", "th": "", @@ -78,7 +78,7 @@ "Thu": "", "Thursday": "Perşembe", "Tue": "", - "Tuesday": "", + "Tuesday": "Salı", "Wed": "", "Wednesday": "Çarşamba", "Winter": "kış", From 7b3ae571033fcac867a53c693d0c54268021d247 Mon Sep 17 00:00:00 2001 From: j Date: Fri, 4 Aug 2023 17:46:25 +0200 Subject: [PATCH 07/33] update turkish translations --- source/Ox/json/locale.tr.json | 36 +-- source/UI/json/locale.tr.json | 542 +++++++++++++++++----------------- 2 files changed, 289 insertions(+), 289 deletions(-) diff --git a/source/Ox/json/locale.tr.json b/source/Ox/json/locale.tr.json index 2a9b1f0b..81c526bc 100644 --- a/source/Ox/json/locale.tr.json +++ b/source/Ox/json/locale.tr.json @@ -4,16 +4,16 @@ ".": ".", "%A, %B %e, %Y": "%A, %B %e, %Y", "%a, %b %e, %Y": "%a, %b %e, %Y", - "AD": "", + "AD": "MS", "AM": "", "Apr": "", "April": "Nisan", "Aug": "", "August": "Ağustos", - "BC": "", + "BC": "MÖ", "%B %e, %Y": "", "%b %e, %Y": "", - "d": "", + "d": "g", "day": "gün", "days": "gün", "days{2}": "gün", @@ -24,7 +24,7 @@ "February": "Şubat", "Fri": "", "Friday": "Cuma", - "h": "", + "h": "s", "hour": "saat", "hours": "saat", "hours{2}": "saat", @@ -36,7 +36,7 @@ "July": "Temmuz", "Jun": "", "June": "Haziran", - "m": "", + "m": "d", "Mar": "", "March": "Mart", "May": "Mayıs", @@ -47,17 +47,17 @@ "minutes{2}": "dakika", "Mon": "", "Monday": "Pazartesi", - "nd": "", - "nd{22}": "", - "no": "", + "nd": ".", + "nd{22}": ".", + "no": "hayır", "Nov": "", "November": "Kasım", "Oct": "", "October": "Ekim", "PM": "", - "rd": "", - "rd{23}": "", - "s": "", + "rd": ".", + "rd{23}": ".", + "s": "s", "Sat": "", "Saturday": "Cumartesi", "second": "saniye", @@ -66,15 +66,15 @@ "Sep": "", "September": "Eylül", "Spring": "Bahar", - "st": "", - "st{21}": "", + "st": ".", + "st{21}": ".", "Summer": "Yaz", "Sun": "", "Sunday": "Pazar", - "th": "", - "th{11}": "", - "th{12}": "", - "th{13}": "", + "th": ".", + "th{11}": ".", + "th{12}": ".", + "th{13}": ".", "Thu": "", "Thursday": "Perşembe", "Tue": "", @@ -82,7 +82,7 @@ "Wed": "", "Wednesday": "Çarşamba", "Winter": "kış", - "y": "", + "y": "yy", "year": "yıl", "years": "yıl", "years{2}": "yıl" diff --git a/source/UI/json/locale.tr.json b/source/UI/json/locale.tr.json index f5f82983..0aa7149b 100644 --- a/source/UI/json/locale.tr.json +++ b/source/UI/json/locale.tr.json @@ -4,278 +4,278 @@ "Add Files": "Dosya Ekle", "Add Place": "Yer Ekle", "Add a condition": "koşul ekle", - "Add a group of conditions": "", + "Add a group of conditions": "Bir grup koşul ekle", "Add column after": "Sonrasına sütun ekle", - "Add column before": "", - "Add row above": "", - "Add row below": "", - "Add {0}": "", - "Adding...": "", + "Add column before": "Önce sütun ekle", + "Add row above": "Üste satır ekle", + "Add row below": "Altına satır ekle", + "Add {0}": "{0} ekle", + "Adding...": "Ekleniyor..", "All": "Tüm", - "Alternative Names": "", - "Area": "", - "At Current Position": "", - "Blockquote": "", - "Bold": "", - "Borough": "", - "Building": "", - "Bullets": "", - "By Duration": "", - "By Position": "", - "By Text": "", - "Cancel": "", - "Cancel/Deselect": "", - "Cancelled": "", - "City": "", - "Clear": "", - "Clear Event": "", - "Clear Place": "", - "Clearing...": "", - "Click to hide": "", - "Click to pan, doubleclick to zoom": "", - "Click to select": "", - "Click to select, doubleclick to edit": "", - "Click to show": "", - "Close": "", - "Complete": "", - "Country": "", - "Date": "", - "Date Created": "", - "Date Modified": "", - "Define": "", - "Define Event": "", - "Define Place": "", - "Delete Annotation": "", - "Deselect": "", - "Deselect Annotation": "", - "Don't Shuffle": "", - "Done": "", - "Download": "", - "Download Selection...": "", - "Download Video...": "", - "Drag to resize": "", - "Drag to resize or click to hide": "", - "Drag to resize or click to toggle map": "", - "Duration": "", - "East": "", - "Edit": "Düzenlemek", - "Edit Annotation": "", - "Edit/Submit": "", - "Editing Options": "", - "Embed Selection...": "", - "End": "", - "Enter Fullscreen": "", - "Event": "", - "Events": "", - "Examples...": "", - "Exit Fullscreen": "", - "Feature": "", - "Find": "", - "Find in All {0}": "", - "Find in List": "", - "Find in This {0}": "", - "Find on Map": "", - "Find...": "", - "Find: All": "", - "Find: Alternative Names": "", - "Find: Geoname": "", - "Find: Name": "", - "Flag": "", - "Font Size": "", - "Generating Documentation...": "", - "Geoname": "", - "Go One Frame Back": "", - "Go One Frame Forward": "", - "Go One Line Down": "", - "Go One Line Up": "", - "Go One Second Back": "", - "Go One Second Forward": "", - "Go to First Frame": "", - "Go to In Point": "", - "Go to Last Frame": "", - "Go to Next Annotation": "", - "Go to Next Cut": "", - "Go to Next Result": "", - "Go to Out Point": "", - "Go to Poster Frame": "", - "Go to Previous Annotation": "", - "Go to Previous Cut": "", - "Go to Previous Result": "", - "Headline": "", - "Hide": "", - "Hide Controls": "", - "Hide Labels": "", - "Home": "", - "Home Channel": "", - "Image": "", - "Import Annotations...": "", - "In Current Selection": "", - "Insert": "", - "Insert HTML": "", - "Insert...": "", - "Italic": "", - "Join Clip(s) at Cuts": "", - "Keyboard Shortcuts": "", - "Keyboard Shortcuts...": "", - "Large": "", - "Large Player": "", - "Larger": "", - "Latitude": "", - "Limit to": "", - "Linebreak": "", - "Link": "", - "List": "", - "Longitude": "", - "Make Clip(s) Static": "", - "Map Options": "", - "Match": "", - "Matches": "", - "Medium": "", - "Monospace": "", - "Mute": "", - "Mute/Unmute": "", - "Name": "", - "New Event": "", - "New Place": "", - "Next": "", - "Next Channel": "", - "Next Result": "", - "No file selected": "", - "No files selected": "", - "North": "", - "Numbers": "", - "Open in New Tab": "", - "Options": "", - "Other": "", - "Paragraph": "", - "Pause": "", - "Paused": "", - "Person": "", - "Place": "", - "Place or Event": "", - "Play": "", - "Play Current Track": "", - "Play In to Out": "", - "Play Next Track": "", - "Play/Pause": "", - "Previous": "", - "Previous Channel": "", - "Previous Result": "", - "Region": "", - "Reload": "", - "Remove": "", - "Remove Event": "", - "Remove File": "", - "Remove Place": "", - "Remove this column": "", - "Remove this condition": "", - "Remove this group of conditions": "", - "Remove this row": "", - "Removing...": "", - "Repeat All": "", - "Repeat None": "", - "Repeat One": "", - "Reset this condition": "", - "Resolution": "", - "Restart": "", - "Restore Defaults": "", - "Results": "", - "Resume": "", - "Right-to-Left": "", - "Run Tests": "", - "Save Changes": "", - "Save as Smart List": "", - "Scale to Fill": "", - "Scale to Fit": "", - "Scroll to Player": "", - "Select Current Annotation": "", - "Select Current Cut": "", - "Select File": "", - "Select Next Annotation": "", - "Select Previous Annotation": "", - "Set ": "", - "Set In Point": "", - "Set Out Point": "", - "Set Poster Frame": "", - "Settings": "", - "Show Annotations": "", - "Show Controls": "", - "Show Dates": "", - "Show Labels": "", - "Show Other": "", - "Show People": "", - "Show Places": "", - "Show Remaining Time": "", - "Show Subtitles": "", - "Show Users": "", - "Shuffle": "", - "Small": "", - "Small Player": "", - "Smaller": "", - "Sort Annotations": "", - "South": "", - "Split Clip(s) at Cuts": "", - "Start": "", - "Street": "", - "Strike": "", - "Subscript": "", - "Subtitles": "", - "Superscript": "", - "Switch Theme": "", - "Timeline": "", - "Title": "", - "Turn Volume Down": "", - "Turn Volume Up": "", - "Type": "", - "Underline": "", - "Undo Changes": "", - "Unmute": "", - "Untitled": "", - "User": "", - "Valid": "", - "View": "", - "View Live": "", - "View Source": "", - "View as Grid": "", - "View as List": "", - "Volume": "", - "West": "", - "add": "", - "all": "", - "and": "", - "annotations": "", - "any": "", - "ascending": "", - "bracket": "", - "contains": "", - "descending": "", - "does not contain": "", - "does not end with": "", - "does not start with": "", - "ends with": "", - "file": "", - "files": "", - "in": "", + "Alternative Names": "Alternatif İsimler", + "Area": "Alan", + "At Current Position": "Mevcut Konumda", + "Blockquote": "Blok halinde alıntıla", + "Bold": "Kalın", + "Borough": "Mahalle", + "Building": "Bina", + "Bullets": "Madde İşaretleri", + "By Duration": "Süreye göre", + "By Position": "Pozisyona Göre", + "By Text": "Metne gore", + "Cancel": "İptal et", + "Cancel/Deselect": "İptal et/Seçimi kaldır", + "Cancelled": "İptal edildi", + "City": "Şehir", + "Clear": "Temizle", + "Clear Event": "Etkinliği temizle", + "Clear Place": "Yeri Temizle", + "Clearing...": "Temizleniyor", + "Click to hide": "gizlemek için tıklayın", + "Click to pan, doubleclick to zoom": "Kaydırmak için tıklayın, yakınlaştırmak için çift tıklayın", + "Click to select": "Seçmek için tıklayın", + "Click to select, doubleclick to edit": "Seçmek için tıklayın, düzenlemek için çift tıklayın", + "Click to show": "Göstermek için tıklayın", + "Close": "Kapat", + "Complete": "Tamamlandı", + "Country": "Ülke", + "Date": "Tarih", + "Date Created": "Oluşturulma Tarihi", + "Date Modified": "Değiştirilme Tarihi", + "Define": "Tanımla", + "Define Event": "Etkinlik Tanımla", + "Define Place": "Yer Tanımla", + "Delete Annotation": "Ek Açıklamayı Sil", + "Deselect": "Seçimi Kaldır", + "Deselect Annotation": "Ek Açıklamanın Seçimini Kaldır", + "Don't Shuffle": "Karıştırma", + "Done": "Bitti", + "Download": "İndir", + "Download Selection...": "Seçimi İndir...", + "Download Video...": "Video İndir", + "Drag to resize": "Yeniden boyutlandırmak için sürükleyin", + "Drag to resize or click to hide": "Yeniden boyutlandırmak için sürükleyin veya gizlemek için tıklayın", + "Drag to resize or click to toggle map": "eniden boyutlandırmak için sürükleyin veya haritayı değiştirmek için tıklayın", + "Duration": "Süre", + "East": "Doğu", + "Edit": "Kurgula", + "Edit Annotation": "Ek Açıklamayı Kurgula", + "Edit/Submit": "Kurgula/Gönder", + "Editing Options": "Kurgulama Seçenekleri", + "Embed Selection...": "Seçimi Yerleştir", + "End": "Son", + "Enter Fullscreen": "Tam Ekran Gir", + "Event": "Etkinlik", + "Events": "Etkinlikler", + "Examples...": "Örnekler...", + "Exit Fullscreen": "Tam Ekrandan Çık", + "Feature": "Özellik", + "Find": "Bul", + "Find in All {0}": "Tüm {0} İçinde Bul", + "Find in List": "Listede Bul", + "Find in This {0}": "Bu {0}'da Bul", + "Find on Map": "Haritada Bul", + "Find...": "Bul...", + "Find: All": "Bul: Tümü", + "Find: Alternative Names": "Bul: Alternatif İsimler", + "Find: Geoname": "Bul: Geoisim", + "Find: Name": "Bul: İsim", + "Flag": "Bayrak", + "Font Size": "Yazı Tipi Boyutu", + "Generating Documentation...": "Dokümantasyon Oluşturuyor...", + "Geoname": "Geo isim", + "Go One Frame Back": "Bir Kare Geri Git", + "Go One Frame Forward": "Bir Kare İleri Git", + "Go One Line Down": "Bir Satır Aşağı Git", + "Go One Line Up": "Bir Satır Yukarı Git", + "Go One Second Back": "Bir Saniye Geri Git", + "Go One Second Forward": "Bir Saniye İleriye Git", + "Go to First Frame": "İlk Kareye Git", + "Go to In Point": "Giriş Noktasına Git", + "Go to Last Frame": "Son Kareye Git", + "Go to Next Annotation": "Sonraki Açıklamaya Git", + "Go to Next Cut": "Sonraki Kesime Git", + "Go to Next Result": "Sonraki Sonuca Git", + "Go to Out Point": "Çıkış Noktasına Git", + "Go to Poster Frame": "Poster Çerçevesine Git", + "Go to Previous Annotation": "Önceki Ek Açıklamaya Git", + "Go to Previous Cut": "Önceki Kesmeye Git", + "Go to Previous Result": "Önceki Sonuca Git", + "Headline": "Başlık", + "Hide": "Sakla", + "Hide Controls": "Kontrolleri Gizle", + "Hide Labels": "Etiketleri Gizle", + "Home": "Ana Sayfa", + "Home Channel": "Ana Kanal", + "Image": "İmge", + "Import Annotations...": "Ek Açıklamaları İçe Aktar...", + "In Current Selection": "Mevcut Seçimde", + "Insert": "Ekle", + "Insert HTML": "HTML Ekle", + "Insert...": "Ekle...", + "Italic": "İtalik", + "Join Clip(s) at Cuts": "Klip(ler)i Kesimlerde Birleştir", + "Keyboard Shortcuts": "Klavye Kısayolları", + "Keyboard Shortcuts...": "Klavye Kısayolları...", + "Large": "Büyük", + "Large Player": "Büyük Oynatıcı", + "Larger": "Daha Büyük", + "Latitude": "Enlem", + "Limit to": "Sınırla", + "Linebreak": "Satır Sonu", + "Link": "Link", + "List": "Liste", + "Longitude": "Boylam", + "Make Clip(s) Static": "Klip(ler)i Statik Yap", + "Map Options": "Harita Seçenekleri", + "Match": "Eşleme", + "Matches": "Eşlemeler", + "Medium": "Orta", + "Monospace": "Monospace", + "Mute": "Sessiz", + "Mute/Unmute": "Sesi Kapat/Sesi Aç", + "Name": "İsim", + "New Event": "Yeni Etkinlik", + "New Place": "Yeni Yer", + "Next": "Sıradaki", + "Next Channel": "Yeni Kanal", + "Next Result": "Bir Sonraki Sonuç", + "No file selected": "Seçili dosya yok", + "No files selected": "Seçili dosya yok", + "North": "Kuzey", + "Numbers": "Sayılar", + "Open in New Tab": "Yeni Sekmede Aç", + "Options": "Seçenekler", + "Other": "Diğer", + "Paragraph": "Paragraf", + "Pause": "Durdur", + "Paused": "Durduruldu", + "Person": "Kişi", + "Place": "Yer", + "Place or Event": "Yer ya da Etkinlik", + "Play": "Oynat", + "Play Current Track": "Seçili Parçayı Çal", + "Play In to Out": "Giriş-Çıkış Arasında Oynat", + "Play Next Track": "Bir Sonraki Parçayı Çal", + "Play/Pause": "Başlat/Durdur", + "Previous": "Önceki", + "Previous Channel": "Önceki Kanal", + "Previous Result": "Önceki Sonuç", + "Region": "Bölge", + "Reload": "Yeniden Yükle", + "Remove": "Kaldır", + "Remove Event": "Etkinliği Kaldır", + "Remove File": "Dosyayı Kaldır", + "Remove Place": "Yeri Kaldır", + "Remove this column": "Bu sütunu kaldır", + "Remove this condition": "Bu koşulu kaldır", + "Remove this group of conditions": "Bu koşul grubunu kaldır ", + "Remove this row": "Bu satırı kaldır", + "Removing...": "Kaldırıyor...", + "Repeat All": "Hepsini Tekrarla", + "Repeat None": "Hiçbirini Tekrarlama", + "Repeat One": "Birini Tekrarla", + "Reset this condition": "Bu koşulu sıfırla", + "Resolution": "Çözünürlük", + "Restart": "Yeniden Başlat", + "Restore Defaults": "Varsayılanları Geri Yükle", + "Results": "Sonuçlar", + "Resume": "Devam Et", + "Right-to-Left": "Sağdan Sola", + "Run Tests": "Testleri Çalıştır", + "Save Changes": "Değişiklikleri Kaydet", + "Save as Smart List": "Akıllı Liste olarak Kaydet", + "Scale to Fill": "Doldurmak için Ölç", + "Scale to Fit": "Sığacak Şekilde Ölç", + "Scroll to Player": "Oyuncuya Kaydır", + "Select Current Annotation": "Geçerli Ek Açılamayı Seç", + "Select Current Cut": "Geçerli Kesimi Seç", + "Select File": "Dosya Seç", + "Select Next Annotation": "Sonraki Açıklamayı Seç", + "Select Previous Annotation": "Önceki Açıklamayı Seç", + "Set ": "Berlie", + "Set In Point": "Giriş Noktası Belirle", + "Set Out Point": "Çıkış Noktası Belirle", + "Set Poster Frame": "Poster Karesi Belirle", + "Settings": "Ayarlar", + "Show Annotations": "Ek Açıklamayı Göster", + "Show Controls": "Kontrol Çubuğunu Göster", + "Show Dates": "Tarihleri Göster", + "Show Labels": "Etiketleri Göster", + "Show Other": "Diğerini Göster", + "Show People": "İnsanları Göster", + "Show Places": "Yerleri Göster", + "Show Remaining Time": "Kalan Zamanı Göster", + "Show Subtitles": "Altyazıları Göster", + "Show Users": "Kullanıcıları Göster", + "Shuffle": "Karıştır", + "Small": "Küçük", + "Small Player": "Küçük Oynatıcı", + "Smaller": "Daha Küçük", + "Sort Annotations": "Ek Açıklamayı Düzenle", + "South": "Güney", + "Split Clip(s) at Cuts": "Klip(ler)i Kesimlerde Böl", + "Start": "Başlat", + "Street": "Sokak", + "Strike": "Üstünü Çiz", + "Subscript": "Alt Simge", + "Subtitles": "Altyazılar", + "Superscript": "Üst Simge", + "Switch Theme": "Temayı Değiştir", + "Timeline": "Zaman Çizelgesi", + "Title": "Başlık", + "Turn Volume Down": "Sesi Kıs", + "Turn Volume Up": "Sesi Aç", + "Type": "Tür", + "Underline": "Altını Çiz", + "Undo Changes": "Değişiklikleri Geri Al", + "Unmute": "Sesi Aç", + "Untitled": "İsimsiz", + "User": "Kullanıcı", + "Valid": "Geçerli", + "View": "Görüntüle", + "View Live": "Canlı Görüntüle", + "View Source": "Kaynağı Görüntüle", + "View as Grid": "Izgara Olarak Görüntüle" + "View as List": "Liste Olarak Görüntüle", + "Volume": "Ses Seviyesi", + "West": "Batı", + "add": "ekle", + "all": "hepsi", + "and": "ve", + "annotations": "ek açıklamalar", + "any": "herhangi", + "ascending": "artan", + "bracket": "parantez", + "contains": "içerir", + "descending": "azalan", + "does not contain": "içermez", + "does not end with": "ile bitmiyor", + "does not start with": "ile başlamaz", + "ends with": "ile biter", + "file": "dosya", + "files": "dosyalar", + "in": "içinde", "is": "", - "is after": "", - "is before": "", - "is between": "", - "is greater than": "", - "is less than": "", - "is not": "", - "is not after": "", - "is not before": "", - "is not between": "", - "is not greater than": "", - "is not less than": "", - "items": "", - "of the following conditions": "", - "order": "", - "sorted by": "", - "starts with": "", - "unknown": "", - "{0} Century": "", - "{0} Century BC": "", - "{0} Millennium": "", - "{0} Millennium BC": "" + "is after": "sonra", + "is before": "önce", + "is between": "arasında", + "is greater than": "'den büyüktür", + "is less than": "daha azdır", + "is not": "değil", + "is not after": "sonra değil", + "is not before": "önce değil", + "is not between": "arasında değil", + "is not greater than": "daha büyük değil", + "is not less than": "daha az değil", + "items": "öğeler", + "of the following conditions": "aşağıdaki koşullardan", + "order": "sırala", + "sorted by": "göre sırala", + "starts with": "ile başla", + "unknown": "bilinmiyor", + "{0} Century": "Yüzyıl", + "{0} Century BC": "Yüzyıl MÖ", + "{0} Millennium": "{0} Milenyum", + "{0} Millennium BC": "MÖ {0} Milenyum" } From b11dd36c7a7eee85f8fd245faef04202aa5240af Mon Sep 17 00:00:00 2001 From: j Date: Fri, 4 Aug 2023 17:48:11 +0200 Subject: [PATCH 08/33] typo --- source/UI/json/locale.tr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/UI/json/locale.tr.json b/source/UI/json/locale.tr.json index 0aa7149b..7f671f86 100644 --- a/source/UI/json/locale.tr.json +++ b/source/UI/json/locale.tr.json @@ -236,7 +236,7 @@ "View": "Görüntüle", "View Live": "Canlı Görüntüle", "View Source": "Kaynağı Görüntüle", - "View as Grid": "Izgara Olarak Görüntüle" + "View as Grid": "Izgara Olarak Görüntüle", "View as List": "Liste Olarak Görüntüle", "Volume": "Ses Seviyesi", "West": "Batı", From ac1a4ef961e6e2d4a6c383579fbd7d30c6345b8c Mon Sep 17 00:00:00 2001 From: j Date: Sun, 6 Aug 2023 11:49:55 +0200 Subject: [PATCH 09/33] avoid memory leak in jQuery.cache --- source/UI/js/Core/Element.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/UI/js/Core/Element.js b/source/UI/js/Core/Element.js index 5e1ca456..df17ea0b 100644 --- a/source/UI/js/Core/Element.js +++ b/source/UI/js/Core/Element.js @@ -710,6 +710,7 @@ Ox.Focus.removeElement(this.oxid); this.self(_).unbindKeyboard(); this.$tooltip && this.$tooltip.remove(); + jQuery.cleanData(this.$element); delete Ox.$elements[this.oxid]; // If setElement($element) was used, delete $element too delete Ox.$elements[this.$element.oxid]; @@ -738,6 +739,7 @@ this.findElements().forEach(function($element) { $element.removeElement(false); }); + jQuery.cleanData(this.$element); this.$element.replaceWith($element); if ($element.$element) { // $element is Ox.Element this.$element = $element.$element; From 49742b8b1ace90dd042de3a2e9e0532d32c51a8d Mon Sep 17 00:00:00 2001 From: j Date: Sun, 3 Sep 2023 11:24:01 +0100 Subject: [PATCH 10/33] Ox.FormPanel.values now accepts values too --- source/UI/js/Form/FormPanel.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/source/UI/js/Form/FormPanel.js b/source/UI/js/Form/FormPanel.js index b6b3409c..63755307 100644 --- a/source/UI/js/Form/FormPanel.js +++ b/source/UI/js/Form/FormPanel.js @@ -191,13 +191,24 @@ Ox.FormPanel = function(options, self) { values values @*/ that.values = function() { - var values = {}; - self.options.form.forEach(function(section, i) { - values[section.id] = self.$forms[i].values(); - }); - return values; + if (arguments.length === 0) { + var values = {}; + self.options.form.forEach(function(section, i) { + values[section.id] = self.$forms[i].values(); + }); + return values; + } else { + var sections = arguments[0]; + + self.options.form.forEach(function(form, i) { + if ((form.id in sections) { + self.$forms[i].values(sections[form.id]); + } + }); + } }; + return that; }; From 6252e27f6c2f75bdc55f3585b60f2d75220d6df7 Mon Sep 17 00:00:00 2001 From: j Date: Sun, 3 Sep 2023 11:44:49 +0100 Subject: [PATCH 11/33] Ox.FormPanel: support setting inital and updating selected panel --- source/UI/js/Form/FormPanel.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/source/UI/js/Form/FormPanel.js b/source/UI/js/Form/FormPanel.js index 63755307..ca9f22f2 100644 --- a/source/UI/js/Form/FormPanel.js +++ b/source/UI/js/Form/FormPanel.js @@ -15,11 +15,18 @@ Ox.FormPanel = function(options, self) { var that = Ox.Element({}, self) .defaults({ form: [], - listSize: 256 + listSize: 256, + section: null }) - .options(options || {}); + .options(options || {}) + .update({ + section: setSection + }); - self.section = 0; + if (self.options.section === null) { + self.options.section = self.options.form[0].id; + } + self.section = Ox.getIndexById(self.options.form, self.optoins.section); self.sectionTitle = self.options.form[self.section].title; self.$list = Ox.TableList({ columns: [ @@ -62,7 +69,7 @@ Ox.FormPanel = function(options, self) { }), max: 1, min: 1, - selected: [self.options.form[0].id], + selected: [self.options.selected], sort: [{key: 'id', operator: '+'}], unique: 'id', width: self.options.listSize @@ -130,7 +137,18 @@ Ox.FormPanel = function(options, self) { }); }); - self.$sections[0].show(); + self.$sections[self.section].show(); + + function setSection() { + var id = self.options.section, + section = Ox.getIndexById(self.options.form, id); + if (self.section != section) { + self.$sections[self.section].hide(); + self.section = section; + self.$list.options('selected', [id]); + self.$sections[self.section].show(); + } + }; that.setElement(Ox.SplitPanel({ elements: [ From fb6c862b88193d8b3b3a1ef364a225919f972b4c Mon Sep 17 00:00:00 2001 From: j Date: Sun, 3 Sep 2023 11:50:05 +0100 Subject: [PATCH 12/33] typo --- source/UI/js/Form/FormPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/UI/js/Form/FormPanel.js b/source/UI/js/Form/FormPanel.js index ca9f22f2..5b3da686 100644 --- a/source/UI/js/Form/FormPanel.js +++ b/source/UI/js/Form/FormPanel.js @@ -26,7 +26,7 @@ Ox.FormPanel = function(options, self) { if (self.options.section === null) { self.options.section = self.options.form[0].id; } - self.section = Ox.getIndexById(self.options.form, self.optoins.section); + self.section = Ox.getIndexById(self.options.form, self.options.section); self.sectionTitle = self.options.form[self.section].title; self.$list = Ox.TableList({ columns: [ From 9a7c3144f591118e5495b991651b9129d646a745 Mon Sep 17 00:00:00 2001 From: j Date: Sun, 3 Sep 2023 12:11:12 +0100 Subject: [PATCH 13/33] fix FormPanel selections --- source/UI/js/Form/FormPanel.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/source/UI/js/Form/FormPanel.js b/source/UI/js/Form/FormPanel.js index 5b3da686..2770599d 100644 --- a/source/UI/js/Form/FormPanel.js +++ b/source/UI/js/Form/FormPanel.js @@ -69,7 +69,7 @@ Ox.FormPanel = function(options, self) { }), max: 1, min: 1, - selected: [self.options.selected], + selected: [self.options.section], sort: [{key: 'id', operator: '+'}], unique: 'id', width: self.options.listSize @@ -77,6 +77,9 @@ Ox.FormPanel = function(options, self) { select: function(data) { self.$sections[self.section].hide(); self.section = Ox.getIndexById(self.options.form, data.ids[0]); + if (self.section == -1) { + self.section = 0 + } self.$sections[self.section].show(); that.triggerEvent('select', {section: data.ids[0]}); } @@ -142,7 +145,7 @@ Ox.FormPanel = function(options, self) { function setSection() { var id = self.options.section, section = Ox.getIndexById(self.options.form, id); - if (self.section != section) { + if (section > -1 && self.section != section) { self.$sections[self.section].hide(); self.section = section; self.$list.options('selected', [id]); @@ -219,7 +222,7 @@ Ox.FormPanel = function(options, self) { var sections = arguments[0]; self.options.form.forEach(function(form, i) { - if ((form.id in sections) { + if (form.id in sections) { self.$forms[i].values(sections[form.id]); } }); From 2a147446c26df12e8d157c4b7bedf6966753b7b1 Mon Sep 17 00:00:00 2001 From: j Date: Sun, 3 Sep 2023 12:15:05 +0100 Subject: [PATCH 14/33] Ox.formPanel: update options.section on interactive change --- source/UI/js/Form/FormPanel.js | 1 + 1 file changed, 1 insertion(+) diff --git a/source/UI/js/Form/FormPanel.js b/source/UI/js/Form/FormPanel.js index 2770599d..9eb7efe9 100644 --- a/source/UI/js/Form/FormPanel.js +++ b/source/UI/js/Form/FormPanel.js @@ -81,6 +81,7 @@ Ox.FormPanel = function(options, self) { self.section = 0 } self.$sections[self.section].show(); + self.options.section = self.options.form[self.section].id; that.triggerEvent('select', {section: data.ids[0]}); } }); From 2c4e0b8f7b53cadbdba3187ea62efc632247d4ec Mon Sep 17 00:00:00 2001 From: qsniyg Date: Mon, 4 Sep 2023 22:29:38 +0000 Subject: [PATCH 15/33] Use self.pageLength instead of self.options.pageLength in List::getPageByPosition self.options.pageLength is a hint, the actual pageLength can differ. For example when orientation == both, self.options.pageLength is disregarded entirely. --- source/UI/js/List/List.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/UI/js/List/List.js b/source/UI/js/List/List.js index b5421c02..0c0f9bb1 100644 --- a/source/UI/js/List/List.js +++ b/source/UI/js/List/List.js @@ -624,7 +624,7 @@ Ox.List = function(options, self) { } function getPageByPosition(pos) { - return Math.floor(pos / self.options.pageLength); + return Math.floor(pos / self.pageLength); } function getPageByScrollPosition(pos) { From d71ad7cad6b584c1798ed26a75e19122d78c2975 Mon Sep 17 00:00:00 2001 From: qsniyg Date: Mon, 4 Sep 2023 22:36:32 +0000 Subject: [PATCH 16/33] Allow setting multiple values for ButtonGroup --- source/UI/js/Form/ButtonGroup.js | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/source/UI/js/Form/ButtonGroup.js b/source/UI/js/Form/ButtonGroup.js index c1c5ff5e..b4ff9ef1 100644 --- a/source/UI/js/Form/ButtonGroup.js +++ b/source/UI/js/Form/ButtonGroup.js @@ -31,18 +31,29 @@ Ox.ButtonGroup = function(options, self) { .options(options || {}) .update({ value: function() { - // fixme: this doesn't work in cases where - // multiple buttons can be selected - var position = Ox.getIndexById( - self.options.buttons, self.options.value - ); - if (position > -1) { - self.$buttons[position].trigger('click'); - } else if (self.options.min == 0) { - self.$buttons.forEach(function($button, i) { - $button.options('value') && $button.trigger('click'); - }); + var values = Ox.makeArray(self.options.value); + + var positions = []; + Ox.forEach(values, function(value) { + var position = Ox.getIndexById( + self.options.buttons, value + ); + + if (position > -1) { + positions.push(position); + } + }); + + if (positions.length < self.options.min) { + return; } + + Ox.forEach(self.$buttons, function(button, pos) { + var enabled = positions.indexOf(pos) > -1; + if (enabled !== button.value()) { + button.trigger('click'); + } + }); } }) .addClass( From d25fe788ee3d08ac1ef56c9dec8cca902dd17df5 Mon Sep 17 00:00:00 2001 From: j Date: Sat, 7 Oct 2023 10:22:56 +0100 Subject: [PATCH 17/33] group vars into block (coding style) --- source/UI/js/Form/ButtonGroup.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/UI/js/Form/ButtonGroup.js b/source/UI/js/Form/ButtonGroup.js index b4ff9ef1..d8eb06ab 100644 --- a/source/UI/js/Form/ButtonGroup.js +++ b/source/UI/js/Form/ButtonGroup.js @@ -31,9 +31,9 @@ Ox.ButtonGroup = function(options, self) { .options(options || {}) .update({ value: function() { - var values = Ox.makeArray(self.options.value); + var positions = [], + values = Ox.makeArray(self.options.value); - var positions = []; Ox.forEach(values, function(value) { var position = Ox.getIndexById( self.options.buttons, value From fc001a2f442bd94cbf76ae58d7275e195b1e8328 Mon Sep 17 00:00:00 2001 From: j Date: Sat, 17 Feb 2024 14:15:34 +0000 Subject: [PATCH 18/33] version can contain dots --- source/Ox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Ox.js b/source/Ox.js index 16d1ce97..675fbed3 100644 --- a/source/Ox.js +++ b/source/Ox.js @@ -37,7 +37,7 @@ } function getPath() { - var index, regexp = /Ox\.js(\?\d+|)$/, + var index, regexp = /Ox\.js(\?[\d\.]+|)$/, scripts = document.getElementsByTagName('script'), src; for (index = scripts.length - 1; index >= 0; index--) { src = scripts[index].src; From 038ff06766cabd0db8fe23677915012e9d5d48bd Mon Sep 17 00:00:00 2001 From: j Date: Fri, 17 May 2024 16:38:44 +0200 Subject: [PATCH 19/33] balance subtitles --- source/UI/css/UI.css | 1 + 1 file changed, 1 insertion(+) diff --git a/source/UI/css/UI.css b/source/UI/css/UI.css index e6ea4dca..c13be065 100644 --- a/source/UI/css/UI.css +++ b/source/UI/css/UI.css @@ -2458,6 +2458,7 @@ Video font-size: 8px; line-height: 10px; text-align: center; + text-wrap: balance; text-overflow: ellipsis; text-shadow: rgba(0, 0, 0, 1) 1px 1px 1px; color: rgb(255, 255, 255); From a67b633bcf26180f6666e2f414fe34d0a1e0c2ab Mon Sep 17 00:00:00 2001 From: j Date: Sat, 1 Jun 2024 10:12:33 +0100 Subject: [PATCH 20/33] scrollbar-color breaks scrollbars on chrome --- source/UI/css/theme.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/source/UI/css/theme.css b/source/UI/css/theme.css index 5c62887e..91427838 100644 --- a/source/UI/css/theme.css +++ b/source/UI/css/theme.css @@ -1188,10 +1188,6 @@ Scrollbars background: -webkit-linear-gradient(left, $buttonActiveGradient); } -body.$themeClass { - scrollbar-color: $bodyBorder $bodyBackground; -} - /* ================================================================================ SourceViewer From f46a70d793794c0fad1ad1d85b460bb1ab801b66 Mon Sep 17 00:00:00 2001 From: j Date: Sun, 2 Jun 2024 16:29:11 +0100 Subject: [PATCH 21/33] re-enable scrollbar-color for non chrome browsers --- source/UI/css/theme.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/UI/css/theme.css b/source/UI/css/theme.css index 91427838..9f359b5e 100644 --- a/source/UI/css/theme.css +++ b/source/UI/css/theme.css @@ -1188,6 +1188,12 @@ Scrollbars background: -webkit-linear-gradient(left, $buttonActiveGradient); } +@supports not selector(::-webkit-scrollbar) { + -body.$themeClass { + scrollbar-color: $bodyBorder $bodyBackground; + } +} + /* ================================================================================ SourceViewer From 1a9fe95530467eb5add495607b6bf62cb225643f Mon Sep 17 00:00:00 2001 From: j Date: Sun, 2 Jun 2024 16:32:12 +0100 Subject: [PATCH 22/33] fix typo --- source/UI/css/theme.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/UI/css/theme.css b/source/UI/css/theme.css index 9f359b5e..83ac746d 100644 --- a/source/UI/css/theme.css +++ b/source/UI/css/theme.css @@ -1189,9 +1189,9 @@ Scrollbars } @supports not selector(::-webkit-scrollbar) { - -body.$themeClass { - scrollbar-color: $bodyBorder $bodyBackground; - } + body.$themeClass { + scrollbar-color: $bodyBorder $bodyBackground; + } } /* From 570fa30d4180a2070f6d03c8c1e2356da750832c Mon Sep 17 00:00:00 2001 From: j Date: Wed, 19 Jun 2024 13:52:52 +0200 Subject: [PATCH 23/33] fix "Find in" for annotations with quotes --- source/UI/js/Video/AnnotationPanel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/UI/js/Video/AnnotationPanel.js b/source/UI/js/Video/AnnotationPanel.js index e93ea671..7e03f779 100644 --- a/source/UI/js/Video/AnnotationPanel.js +++ b/source/UI/js/Video/AnnotationPanel.js @@ -325,9 +325,9 @@ Ox.AnnotationPanel = function(options, self) { } else if (data.id == 'export') { that.triggerEvent('exportannotations'); } else if (data.id == 'find') { - that.triggerEvent('find', {value: value}); + that.triggerEvent('find', {value: Ox.decodeHTMLEntities(value)}); } else if (data.id == 'findannotations') { - that.triggerEvent('findannotations', {key: key, value: value}); + that.triggerEvent('findannotations', {key: key, value: Ox.decodeHTMLEntities(value)}); } else if (data.id == 'import') { that.triggerEvent('importannotations'); } else if (data.id == 'insert') { From 168cdd691c5953cedc5dd67371c770ed89d5e5a3 Mon Sep 17 00:00:00 2001 From: j Date: Wed, 22 Jan 2025 11:57:37 +0530 Subject: [PATCH 24/33] 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 25/33] 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 26/33] 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 27/33] 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 28/33] 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 29/33] 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 30/33] 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 31/33] 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, From f8e48f54ca8d0e0abce1cf14e7097e7fd3fe5020 Mon Sep 17 00:00:00 2001 From: j Date: Sun, 6 Jul 2025 20:23:54 +0100 Subject: [PATCH 32/33] expose async flag to disable it for window.unload events --- source/UI/js/Core/Request.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/UI/js/Core/Request.js b/source/UI/js/Core/Request.js index cadc6e5a..b5980252 100644 --- a/source/UI/js/Core/Request.js +++ b/source/UI/js/Core/Request.js @@ -25,6 +25,7 @@ Ox.Request = (function() { $element; return { + async: true, /*@ bindEvent Bind event @@ -139,6 +140,7 @@ Ox.Request = (function() { } else { pending[req] = true; $.ajax({ + async: Ox.Request.async, beforeSend: function(request) { var csrftoken = Ox.Cookies('csrftoken'); if (csrftoken) { From 33a7832d64ebe6f8e10797a114406c43e0727edf Mon Sep 17 00:00:00 2001 From: j Date: Tue, 5 Aug 2025 15:22:21 +0200 Subject: [PATCH 33/33] raw regexp --- tools/build/build.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tools/build/build.py b/tools/build/build.py index 8accbd3d..ec3cc568 100755 --- a/tools/build/build.py +++ b/tools/build/build.py @@ -80,13 +80,13 @@ def build_oxjs(downloads=False, geo=False): path = source_path + 'UI/svg/' for filename in [filename for filename in os.listdir(path) if not filename[0] in '._']: svg = read_text(path + filename) - svg = re.sub('\n\s*', '', svg) - svg = re.sub('', '', svg) + svg = re.sub(r'\n\s*', '', svg) + svg = re.sub(r'', '', svg) # end fix ui_images[filename[:-4]] = svg if filename.startswith('symbolLoading'): for theme in themes: - theme_svg = re.sub('#808080', format_hex(theme_data[theme]['symbolDefaultColor']), svg) + theme_svg = re.sub(r'#808080', format_hex(theme_data[theme]['symbolDefaultColor']), svg) write_file('%sUI/themes/%s/svg/%s' % (dev_path, theme, filename), theme_svg) write_file('%sUI/themes/%s/svg/%s' % (min_path, theme, filename), theme_svg) @@ -101,10 +101,10 @@ def build_oxjs(downloads=False, geo=False): and (geo or '/Geo/' not in path): # write copies in min path source = os.path.join(path, filename) - is_jquery = re.search('^jquery-[\d\.]+\.js$', filename) - is_jquery_min = re.search('^jquery-[\d\.]+\.min\.js$', filename) - is_jquery_plugin = re.search('^jquery\..*?\.js$', filename) - is_jsonc = re.search('\.jsonc$', filename) + is_jquery = re.search(r'^jquery-[\d\.]+\.js$', filename) + is_jquery_min = re.search(r'^jquery-[\d\.]+\.min\.js$', filename) + is_jquery_plugin = re.search(r'^jquery\..*?\.js$', filename) + is_jsonc = re.search(r'\.jsonc$', filename) if is_jquery or is_jquery_min: target = os.path.join(path.replace(source_path, min_path), 'jquery.js') else: @@ -113,7 +113,7 @@ def build_oxjs(downloads=False, geo=False): ui_files['dev'].append(target.replace(min_path, '')) ui_files['min'].append(target.replace(min_path, '')) if '/Ox/js/' not in source and '/UI/js/' not in source and not is_jquery: - if re.match('^Ox\..+\.js$', filename) or is_jsonc: + if re.match(r'^Ox\..+\.js$', filename) or is_jsonc: js = read_text(source) print('minifiy and write', filename, target) write_file(target, ox.js.minify(js, '' if is_jsonc else comment)) @@ -129,7 +129,7 @@ def build_oxjs(downloads=False, geo=False): if not is_jquery_min: write_link(link_source, link_target) # locales - match = re.search('/(\w+)/json/locale.(\w+).json', source) + match = re.search(r'/(\w+)/json/locale.(\w+).json', source) if match: module = match.group(1) locale = match.group(2) @@ -182,12 +182,12 @@ def build_oxjs(downloads=False, geo=False): if not js_dir + filename in sum(ox_files, []): ox_files[-1].append(js_dir + filename) js = re.sub( - 'Ox.LOCALES = \{\}', + r'Ox.LOCALES = \{\}', 'Ox.LOCALES = ' + json.dumps(locales, indent=4, sort_keys=True), js ) js = re.sub( - "Ox.VERSION = '([\d\.]+)'", + r"Ox.VERSION = '([\d\.]+)'", "Ox.VERSION = '%s'" % version, js ) @@ -238,7 +238,7 @@ def build_oxjs(downloads=False, geo=False): data = { # sum(list, []) is flatten 'documentation': sorted(sum(ox_files, [])) + sorted(list(filter( - lambda x: re.search('\.js$', x), + lambda x: re.search(r'\.js$', x), ui_files['dev'] )) + ['%s/%s.js' % (x, x) for x in ['Geo', 'Image', 'Unicode']]), 'examples': sorted(sum(map( @@ -250,7 +250,7 @@ def build_oxjs(downloads=False, geo=False): ) )), list(filter( - lambda x: not re.search('^[._]', x), + lambda x: not re.search(r'^[._]', x), os.listdir(root_path + 'examples/') )) ), [])) if os.path.exists(root_path + 'examples/') else (), @@ -264,7 +264,7 @@ def build_oxjs(downloads=False, geo=False): 'title': get_title(root_path + 'readme/' + x) }, filter( - lambda x: not re.search('^[._]', x) and re.search('\.html$', x), + lambda x: not re.search(r'^[._]', x) and re.search(r'\.html$', x), os.listdir(root_path + 'readme/') ) )) @@ -355,7 +355,7 @@ def parse_css(css, values): ) for vals in value] ) return string - return re.sub('\$(\w+)(\[\d+\])?', sub, css) + return re.sub(r'\$(\w+)(\[\d+\])?', sub, css) def read_file(file): print('reading', file)