Narrative web: Add popup to manage markers

If you click on a marker in the family map page, you get a popup.
In this popup associated with a place, if you have several events,
for each event you see the person and the event type.
If you click on the person, you go to the related page for this person.
If you click on the event type, you go to the related page for this event.

Fixes #11150
This commit is contained in:
SNoiraud 2019-07-07 19:25:42 +02:00
parent ce4cd33139
commit 9da8f705f6
5 changed files with 316 additions and 111 deletions

View File

@ -66,3 +66,48 @@ div#FamilyMapDetail div#references table.infolist {
div#FamilyMapDetail div#references table.infolist tbody tr td.ColumnPlace {
width: 40%;
}
/* Subsection: popup
------------------------------------------------------ */
.ol-popup {
position: absolute;
background-color: white;
-webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
padding: 15px;
border-radius: 10px;
border: 2px solid #111111;
bottom: 12px;
left: -50px;
min-width: 450px;
}
.ol-popup:after, .ol-popup:before {
top: 100%;
border: solid transparent;
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
}
.ol-popup-closer:after {
content: "✖";
}

View File

@ -742,7 +742,7 @@ class BasePage: # pylint: disable=C1001
event_date = event.get_date_object()
# 0 = latitude, 1 = longitude, 2 - placetitle,
# 3 = place handle, 4 = event date, 5 = event type
# 3 = place handle, 4 = event
found = any(data[3] == place_handle and data[4] == event_date
for data in place_lat_long)
if not found:
@ -754,7 +754,7 @@ class BasePage: # pylint: disable=C1001
if latitude is not None:
etype = event.get_type()
place_lat_long.append([latitude, longitude, placetitle,
place_handle, event_date, etype])
place_handle, event])
def _get_event_place(self, person, place_lat_long):
"""

View File

@ -120,16 +120,27 @@ DROPMASTERS = """
function addMarker() {
var location = tracelife[iterator];
var myLatLng = new google.maps.LatLng(location[1], location[2]);
var infoWindow = new google.maps.InfoWindow;
markers.push(new google.maps.Marker({
var marker = new google.maps.Marker({
position: myLatLng,
map: map,
draggable: true,
title: location[0],
animation: google.maps.Animation.DROP
}));
});
markers.push(marker);
iterator++;
}"""
var title = "<h2>" + location[0] + "</h2>"
bindInfoWindow(marker, map, infoWindow, title+location[4]);
}
function bindInfoWindow(marker, map, infoWindow, html) {
google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(html);
infoWindow.open(map, marker);
});
}
"""
# javascript for Google's Markers...
MARKERS = """
@ -153,6 +164,7 @@ MARKERS = """
function addMarkers() {
var bounds = new google.maps.LatLngBounds();
var infoWindow = new google.maps.InfoWindow;
for (var i = 0; i < tracelife.length; i++) {
var location = tracelife[i];
@ -165,10 +177,20 @@ MARKERS = """
map: map,
zIndex: location[3]
});
var title = "<h2>" + location[0] + "</h2>"
bindInfoWindow(marker, map, infoWindow, title+location[4]);
bounds.extend(myLatLng);
if ( i > 1 ) { map.fitBounds(bounds); };
}
}"""
}
function bindInfoWindow(marker, map, infoWindow, html) {
google.maps.event.addListener(marker, 'click', function() {
infoWindow.setContent(html);
infoWindow.open(map, marker);
});
}
"""
# javascript for OpenStreetMap's markers...
"""
@ -178,7 +200,7 @@ https://openlayers.org/en/latest/examples/
OSM_MARKERS = """
function initialize(){
var map;
var tracelife = %s;
var tracelife = %s
var iconStyle = new ol.style.Style({
image: new ol.style.Icon(({
opacity: 1.0,
@ -193,6 +215,7 @@ OSM_MARKERS = """
geometry: new ol.geom.Point(ol.proj.transform([loc[0], loc[1]],
'EPSG:4326', 'EPSG:3857')),
name: loc[2],
data: loc[3],
});
iconFeature.setStyle(iconStyle);
markerSource.addFeature(iconFeature);
@ -201,11 +224,15 @@ OSM_MARKERS = """
source: markerSource,
style: iconStyle
});
tooltip = new ol.layer.Vector({
source: markerSource,
style: iconStyle
});
var centerCoord = new ol.proj.transform([%s, %s], 'EPSG:4326', 'EPSG:3857');
map = new ol.Map({
target: 'map_canvas',
layers: [new ol.layer.Tile({ source: new ol.source.OSM() }),
markerLayer],
markerLayer, tooltip],
view: new ol.View({ center: centerCoord, zoom: %d })
});
"""
@ -213,85 +240,119 @@ OSM_MARKERS = """
STAMEN_MARKERS = """
function initialize(){
var map;
var tracelife = %s;
var tracelife = %s
var layer = '%s';
var iconStyle = new ol.style.Style({
image: new ol.style.Icon(({
//anchor: [0.5, 46],
anchor: [0.2, 48],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 1.0,
src: marker_png
}))
});
var markerSource = new ol.Collection();
var markerSource = new ol.source.Vector({
});
for (var i = 0; i < tracelife.length; i++) {
var loc = tracelife[i];
var iconFeature = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.transform([loc[0], loc[1]],
'EPSG:4326', 'EPSG:3857')),
name: loc[2],
data: loc[3],
});
iconFeature.setStyle(iconStyle);
markerSource.push(iconFeature);
markerSource.addFeature(iconFeature);
}
var centerCoord = new ol.proj.transform([%s, %s], 'EPSG:4326', 'EPSG:3857');
markerLayer = new ol.layer.Vector({
source: markerSource,
style: iconStyle
});
tooltip = new ol.layer.Vector({
source: markerSource,
style: iconStyle
});
map = new ol.Map({
target: 'map_canvas',
layers: [
new ol.layer.Tile({ source: new ol.source.Stamen({
layer: layer
})
}),
new ol.layer.Vector({ source: new ol.source.Vector({
features: markerSource })
})
],
layer: layer }) }),
markerLayer, tooltip],
view: new ol.View({ center: centerCoord, zoom: %d })
});
"""
OPENLAYER = """
var element = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');
var tip = document.getElementById('tooltip');
var tipcontent = document.getElementById('tooltip-content');
var tooltip = new ol.Overlay({
element: element,
element: tip,
positioning: 'bottom-center',
stopEvent: false
offset: [10, 0],
});
map.addOverlay(tooltip);
var displayFeatureInfo = function(pixel) {
var feature = map.forEachFeatureAtPixel(pixel, function(feature, layer) {
var popup = new ol.Overlay({
element: element,
positioning: 'bottom-center',
autoPan: true,
autoPanAnimation: { duration: 500 },
stopEvent: false
});
map.addOverlay(popup);
/**
* Add a click handler to hide the popup.
* @return {boolean} Don't follow the href.
*/
closer.onclick = function() {
popup.setPosition(undefined);
closer.blur();
return false;
};
map.on('pointermove', function(evt) {
evt.preventDefault()
var feature = this.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
var info = document.getElementById('popup');
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
tooltip.setPosition(coord);
$(element).siblings('.popover').css({ width: '250px' });
$(element).siblings('.popover').css({ background: '#aaa' });
$(info).popover({
'placement': 'auto',
'html': true,
'content': feature.get('name')
});
$(info).popover('show');
} else {
// TODO : some warning with firebug here
$(info).popover('destroy');
$('.popover').remove();
}
};
map.on('pointermove', function(evt) {
map.getTargetElement().style.cursor = feature ? 'pointer' : '';
if (evt.dragging) {
popup.setPosition(undefined);
tooltip.setPosition(undefined);
return;
}
var pixel = map.getEventPixel(evt.originalEvent);
displayFeatureInfo(pixel);
});
map.on('click', function(evt) {
displayFeatureInfo(evt.pixel);
if (feature) {
var coordinate = evt.coordinate;
tipcontent.innerHTML = feature.get('name');
tooltip.setPosition(coordinate);
} else {
tooltip.setPosition(undefined);
}
});
map.on('singleclick', function(evt) {
evt.preventDefault()
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature, layer) {
return feature;
});
if (feature) {
var coordinate = evt.coordinate;
var title = '<h2>' + feature.get('name') + '</h2>';
content.innerHTML = title + feature.get('data');
popup.setPosition(coordinate);
} else {
popup.setPosition(undefined);
}
});
};
"""

View File

@ -52,7 +52,7 @@ import logging
#------------------------------------------------
from gramps.gen.const import GRAMPS_LOCALE as glocale
from gramps.gen.lib import (ChildRefType, Date, Name, Person, EventRoleType,
EventType)
Event, EventType)
from gramps.gen.lib.date import Today
from gramps.gen.plug.report import Bibliography
from gramps.gen.plug.report import utils
@ -121,6 +121,7 @@ class PersonPages(BasePage):
self.sort_name = None
self.googleopts = None
self.googlemapkey = None
self.stamenopts = None
self.birthorder = None
self.person = None
self.familymappages = None
@ -617,6 +618,35 @@ class PersonPages(BasePage):
# and close the file
self.xhtml_writer(indivdetpage, output_file, sio, date)
def _create_family_tracelife(self, tracelife, placetitle,
latitude, longitude, seq_, links):
"""
creates individual family tracelife map events
@param: person -- person from database
@param: links -- used to add links in the popup html page
"""
# are we using Google?
if self.mapservice == "Google":
# are we creating Family Links?
if self.googleopts == "FamilyLinks":
tracelife += """
new google.maps.LatLng(%s, %s),""" % (latitude, longitude)
# are we creating Drop Markers or Markers?
elif self.googleopts in ["Drop", "Markers"]:
tracelife += """
['%s', %s, %s, %d, %s],""" % (placetitle.replace("'", "\\'"), latitude,
longitude, seq_, links)
# are we using OpenStreetMap, Stamen...
else:
tracelife += """
[%f, %f, \'%s\', %s],""" % (float(longitude), float(latitude),
placetitle.replace("'", "\\'"), links)
return tracelife
def __create_family_map(self, person, place_lat_long):
"""
creates individual family map page
@ -640,7 +670,7 @@ class PersonPages(BasePage):
number_markers = len(place_lat_long)
if number_markers > 1:
for (latitude, longitude, placetitle, handle,
date, etype) in place_lat_long:
event) in place_lat_long:
xwidth.append(latitude)
yheight.append(longitude)
xwidth.sort()
@ -680,8 +710,8 @@ class PersonPages(BasePage):
# 0 = latitude, 1 = longitude, 2 = place title,
# 3 = handle, and 4 = date, 5 = event type...
# being sorted by date, latitude, and longitude...
place_lat_long = sorted(place_lat_long, key=itemgetter(4, 0, 1))
# being sorted by place_title
place_lat_long = sorted(place_lat_long, key=itemgetter(2))
# for all plugins
# if family_detail_page
@ -730,61 +760,113 @@ class PersonPages(BasePage):
if number_markers > 0:
tracelife = "["
seq_ = 1
seq_ = 0
for index in range(0, (number_markers - 1)):
(latitude, longitude, placetitle, handle, date,
etype) = place_lat_long[index]
old_place_title = ""
oldevent = None
links = ""
ln_str = "<a href='%s' title='%s' target='_self'>%s</a>"
for index in range(0, number_markers):
(latitude, longitude, placetitle, handle,
event) = place_lat_long[index]
# Do we have several events for this place?
if placetitle == old_place_title:
evthdle = event.get_handle()
bkref_list = self.report.bkref_dict[Event][evthdle]
url_fct = self.report.build_url_fname_html
if bkref_list:
for ref in bkref_list:
(bkref_class, bkref_hdle, role) = ref
if role == "Primary":
url = url_fct(bkref_hdle,
"ppl", self.uplink)
ppl_fct = self.r_db.get_person_from_handle
person = ppl_fct(bkref_hdle)
ppl_lnk = ln_str % (url,
person.get_gramps_id(),
self.get_name(person))
url = self.report.build_url_fname_html(event.get_handle(),
"evt", self.uplink)
evt_type = self._(str(event.get_type()))
evt_date = self.rlocale.get_date(event.get_date_object())
evt_lnk = ln_str % (url, evt_date, evt_type)
# are we using Google?
if self.mapservice == "Google":
links += ' + "</br>%s"' % (ppl_lnk + self._(":") + evt_lnk)
if index == number_markers - 1:
tracelife = self._create_family_tracelife(tracelife,
placetitle,
latitude,
longitude,
seq_,
links)
break
continue
elif old_place_title != "" and index != 0:
(lat, lng, plcetitle, handle_,
event_) = place_lat_long[index-1]
tracelife = self._create_family_tracelife(tracelife,
plcetitle,
lat,
lng,
seq_,
links)
if old_place_title != placetitle:
old_place_title = placetitle
evthdle = event.get_handle()
bkref_list = self.report.bkref_dict[Event][evthdle]
url_fct = self.report.build_url_fname_html
if bkref_list:
for ref in bkref_list:
(bkref_class, bkref_hdle, role) = ref
if role == "Primary":
url = url_fct(bkref_hdle,
"ppl", self.uplink)
ppl_fct = self.r_db.get_person_from_handle
person = ppl_fct(bkref_hdle)
ppl_lnk = ln_str % (url,
person.get_gramps_id(),
self.get_name(person))
url = self.report.build_url_fname_html(event.handle,
"evt",
self.uplink)
evt_type = self._(str(event.get_type()))
evt_date = self.rlocale.get_date(event.get_date_object())
evt_lnk = ln_str % (url, evt_date, evt_type)
# are we creating Family Links?
if self.googleopts == "FamilyLinks":
tracelife += """
new google.maps.LatLng(%s, %s),""" % (latitude, longitude)
# are we creating Drop Markers or Markers?
elif self.googleopts in ["Drop", "Markers"]:
tracelife += """
['%s', %s, %s, %d],""" % (placetitle.replace("'", "\\'"), latitude,
longitude, seq_)
# are we using OpenStreetMap?
links = '"</br>%s"' % (ppl_lnk + self._(":") +
evt_lnk)
elif index == number_markers:
tracelife = self._create_family_tracelife(tracelife,
placetitle,
latitude,
longitude,
seq_,
links)
else:
tracelife += """
[%f, %f, \'%s\'],""" % (float(longitude), float(latitude),
placetitle.replace("'", "\\'"))
evthdle = event.get_handle()
bkref_list = self.report.bkref_dict[Event][evthdle]
url_fct = self.report.build_url_fname_html
if bkref_list:
for ref in bkref_list:
(bkref_class, bkref_hdle, role) = ref
if role == "Primary":
url = url_fct(bkref_hdle,
"ppl", self.uplink)
ppl_fct = self.r_db.get_person_from_handle
person = ppl_fct(bkref_hdle)
ppl_lnk = ln_str % (url,
person.get_gramps_id(),
self.get_name(person))
url = self.report.build_url_fname_html(event.handle,
"evt",
self.uplink)
evt_type = self._(str(event.get_type()))
evt_lnk = ln_str % (url, evt_type, evt_type)
links = '"<p>%s"' % (ppl_lnk + self._(":") + evt_lnk)
old_place_title = placetitle
seq_ += 1
# FIXME: The last element in the place_lat_long list is treated
# specially, and the code above is apparently repeated so as to
# avoid a comma at the end, and get the right closing. This is very
# ugly.
(latitude, longitude, placetitle, handle, date,
etype) = place_lat_long[-1]
# are we using Google?
if self.mapservice == "Google":
# are we creating Family Links?
if self.googleopts == "FamilyLinks":
tracelife += """
new google.maps.LatLng(%s, %s)
];""" % (latitude, longitude)
# are we creating Drop Markers or Markers?
elif self.googleopts in ["Drop", "Markers"]:
tracelife += """
['%s', %s, %s, %d]
];""" % (placetitle.replace("'", "\\'"), latitude, longitude, seq_)
# we are using OpenStreetMap, Stamen...
else:
tracelife += """
[%f, %f, \'%s\']
];""" % (float(longitude), float(latitude), placetitle.replace("'", "\\'"))
tracelife += "];"
# begin MapDetail division...
with Html("div", class_="content", id="FamilyMapDetail") as mapdetail:
outerwrapper += mapdetail
@ -872,13 +954,13 @@ class PersonPages(BasePage):
longitude,
latitude,
10,
)
)
else:
jsc += STAMEN_MARKERS % (tracelife,
self.stamenopts,
midy_, midx_,
zoomlevel,
)
)
jsc += OPENLAYER
# if Google and Drop Markers are selected,
@ -888,8 +970,20 @@ class PersonPages(BasePage):
id="drop", onclick="drop()", inline=True)
# add div for popups.
with Html("div", id="popup", inline=True) as popup:
mapdetail += popup
if self.mapservice == "Google":
with Html("div", id="popup", inline=True) as popup:
mapdetail += popup
else:
with Html("div", id="popup", class_="ol-popup",
inline=True) as popup:
mapdetail += popup
popup += Html("a", href="#", id="popup-closer",
class_="ol-popup-closer")
popup += Html("div", id="popup-content")
with Html("div", id="tooltip", class_="ol-popup",
inline=True) as tooltip:
mapdetail += tooltip
tooltip += Html("div", id="tooltip-content")
# begin place reference section and its table...
with Html("div", class_="subsection", id="references") as section:
@ -917,11 +1011,16 @@ class PersonPages(BasePage):
tbody = Html("tbody")
table += tbody
for (latitude, longitude, placetitle, handle, date,
etype) in place_lat_long:
# being sorted by date
place_lat_long = sorted(place_lat_long,
key=lambda evt:
evt[4].get_date_object())
for (latitude, longitude, placetitle, handle,
event) in place_lat_long:
trow = Html("tr")
tbody += trow
date = event.get_date_object()
trow.extend(
Html("td", data, class_=colclass, inline=True)
for data, colclass in [
@ -929,7 +1028,7 @@ class PersonPages(BasePage):
(self.place_link(handle, placetitle,
uplink=True),
"ColumnPlace"),
(str(etype), "ColumnType")
(str(event.get_type()), "ColumnType")
]
)

View File

@ -426,7 +426,7 @@ class PlacePages(BasePage):
jsc += MARKERS % ([[plce,
latitude,
longitude,
1]],
1,""]],
latitude, longitude,
10)
@ -436,7 +436,7 @@ class PlacePages(BasePage):
jsc += MARKER_PATH % marker_path
jsc += OSM_MARKERS % ([[float(longitude),
float(latitude),
placetitle]],
placetitle,""]],
longitude, latitude, 10)
jsc += OPENLAYER
else: # STAMEN
@ -445,7 +445,7 @@ class PlacePages(BasePage):
jsc += MARKER_PATH % marker_path
jsc += STAMEN_MARKERS % ([[float(longitude),
float(latitude),
placetitle]],
placetitle,""]],
self.stamenopts,
longitude, latitude, 10)
jsc += OPENLAYER