Narrative web: lightbox feature (#1410)

Deature request: #012801
Discourse: https://gramps.discourse.group/t/narrated-website-browsing-media-in-person-page/3195
This commit is contained in:
Serge Noiraud 2023-02-09 09:04:48 +01:00 committed by GitHub
parent 37e0f8968b
commit e4264f837f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 413 additions and 47 deletions

158
data/css/lightbox.css Normal file
View File

@ -0,0 +1,158 @@
/*
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright 2023- Serge Noiraud
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
**************************************************************************************************
GRAMPS cascading style sheet for common lightbox
Style Name: n/a (used by many different styles)
Style Author: Serge Noiraud based on W3C: https://www.w3schools.com/howto/howto_js_lightbox.asp
**************************************************************************************************/
.MediaRow {
z-index: 999;
}
.MediaRow > .MediaColumn {
padding: 0 8px;
}
.MediaRow:after {
content: "";
display: table;
clear: both;
}
.MediaColumn {
float: left;
width: 15%;
height: 100px;
}
/* The Modal (background) */
.ModalClass {
display: none;
position: fixed;
z-index: 900;
padding-top: 5px;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
overflow: auto;
}
/* Modal Content */
.ModalContent {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
width: 95%;
}
/* The Close Button */
.MediaClose {
color: black;
position: absolute;
z-index: 960;
top: 10px;
right: 1%;
font-size: 48px;
font-weight: bold;
}
.MediaClose:hover,
.MediaClose:focus {
color: #999;
text-decoration: none;
cursor: pointer;
}
.MediaSlide {
display: none;
}
.MediaCursor {
cursor: pointer;
}
/* Next & previous buttons */
.MediaPrev,
.MediaNext {
cursor: pointer;
position: fixed;
width: auto;
top: 300px;
padding: 16px;
margin-top: -50px;
color: black;
font-weight: bold;
font-size: 48px;
transition: 0.6s ease;
border-radius: 0 3px 3px 0;
user-select: none;
-webkit-user-select: none;
background-color: rgba(255, 255, 255, 0.3);
}
/* Position the "next button" to the right */
.MediaNext {
right: 2%;
border-radius: 3px 0 0 3px;
}
/* On hover, add a black background color with a little bit see-through */
.MediaPrev:hover,
.MediaNext:hover {
background-color: rgba(0, 0, 0, 0.1);
}
/* Number text (1/3 etc) */
.MediaNumber {
font-size: 36px;
font-weight: bold;
padding: 8px 12px;
position: absolute;
color: black;
background-color: rgba(255, 255, 255, 0.4);
}
/* Less whitespace on smaller real estate. */
@media only screen and (max-width: 1080px) {
.MediaNumber {
font-size: 12px;
}
.MediaPrev,
.MediaNext {
top: 100px;
font-size: 24px;
}
.MediaClose {
font-size: 36px;
}
}
img.hover-shadow {
transition: 0.3s;
}
.hover-shadow:hover {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}

60
data/css/lightbox.js Normal file
View File

@ -0,0 +1,60 @@
/*
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright 2023- Serge Noiraud
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
**************************************************************************************************
GRAMPS javascript for lightbox feature
Style Name: n/a (used by many different styles)
Javascript Author: Serge Noiraud based on W3C: https://www.w3schools.com/howto/howto_js_lightbox.asp
**************************************************************************************************/
// Open the Modal
function openModal() {
document.getElementById("MediaModal").style.display = "block";
}
// Close the Modal
function closeModal() {
document.getElementById("MediaModal").style.display = "none";
}
var slideIndex = 1;
showSlides(slideIndex);
// Next/previous controls
function plusSlides(n) {
showSlides(slideIndex += n);
}
// Thumbnail image controls
function currentSlide(n) {
showSlides(slideIndex = n);
}
function showSlides(n) {
var i;
var slides = document.getElementsByClassName("MediaSlide");
if (n > slides.length) {slideIndex = 1}
if (n < 1) {slideIndex = slides.length}
for (i = 0; i < slides.length; i++) {
slides[i].style.display = "none";
}
slides[slideIndex-1].style.display = "block";
}

View File

@ -2268,15 +2268,19 @@ class BasePage:
# make referenced images have the same order as in media list: # make referenced images have the same order as in media list:
photolist_handles = {} photolist_handles = {}
self.max_img = 0
for mediaref in photolist: for mediaref in photolist:
photolist_handles[mediaref.get_reference_handle()] = mediaref photolist_handles[mediaref.get_reference_handle()] = mediaref
photo_handle = mediaref.get_reference_handle()
photo = self.r_db.get_media_from_handle(photo_handle)
mime_type = photo.get_mime_type()
if "image" in mime_type:
self.max_img += 1
photolist_ordered = [] photolist_ordered = []
for photoref in copy.copy(object_.get_media_list()): for photoref in copy.copy(object_.get_media_list()):
if photoref.ref in photolist_handles: if photoref.ref in photolist_handles:
photo = photolist_handles[photoref.ref] photo = photolist_handles[photoref.ref]
photolist_ordered.append(photo) photolist_ordered.append(photo)
# and add any that are left (should there be any?)
photolist_ordered += photolist
# begin individualgallery division and section title # begin individualgallery division and section title
with Html("div", class_="subsection", id="indivgallery") as section: with Html("div", class_="subsection", id="indivgallery") as section:
@ -2288,46 +2292,120 @@ class BasePage:
with Html("div", style="display:%s" % disp, with Html("div", style="display:%s" % disp,
id="toggle_media") as toggle: id="toggle_media") as toggle:
section += toggle section += toggle
displayed = [] with Html("div", id="medias") as medias:
for mediaref in photolist_ordered: displayed = []
# Create the list of media for lightbox
for mediaref in photolist_ordered:
photo_handle = mediaref.get_reference_handle()
photo = self.r_db.get_media_from_handle(photo_handle)
if photo_handle in displayed:
continue
# get media description
descr = photo.get_description()
mime_type = photo.get_mime_type()
if mime_type and "image" not in mime_type:
try:
# create thumbnail url
# extension needs to be added as it is not
# already there
url_thumb = (self.report.build_url_fname(
photo_handle, "thumb", True,
image=True) + ".png")
# begin hyperlink
medias += self.media_link(photo_handle,
url_thumb, descr,
uplink=self.uplink,
usedescr=True)
except (IOError, OSError) as msg:
self.r_user.warn(_("Could not add photo to page"),
str(msg))
elif not mime_type:
try:
# begin hyperlink
medias += self.doc_link(photo_handle, descr,
uplink=self.uplink)
except (IOError, OSError) as msg:
self.r_user.warn(_("Could not add photo to page"),
str(msg))
# First part for lightbox
lightbox = 0
with Html("div", class_="MediaRow") as lightboxes_1:
for mediaref in photolist_ordered:
photo_handle = mediaref.get_reference_handle()
photo = self.r_db.get_media_from_handle(photo_handle)
mime_type = photo.get_mime_type()
if mime_type and "image" in mime_type:
with Html("div", class_="MediaColumn") as boxes:
lightboxes_1 += boxes
try:
url_thumb = (self.report.build_url_fname(
photo_handle, "thumb",
True, image=True) + ".png")
# begin hyperlink
lightbox += 1
boxes += Html("img", src=url_thumb,
inline=True,
onclick="openModal();currentSlide(%d)" % lightbox,
class_="hover-shadow")
except (IOError, OSError) as msg:
self.r_user.warn(_("Could not add photo to page"),
str(msg))
# Second part for lightbox
lightbox = 0
with Html("div", id="MediaModal",
class_="ModalClass") as lightboxes_2:
lightboxes_2 += Html("span", "&times;",
onclick="closeModal()",
class_="MediaClose MediaCursor",
inline=True)
with Html("div", class_="ModalContent") as lb_content:
lightboxes_2 += lb_content
lb_content += Html("a", "&#10094;",
class_="MediaPrev",
onclick="plusSlides(-1)")
lb_content += Html("a", "&#10095;",
class_="MediaNext",
onclick="plusSlides(1)")
for mediaref in photolist_ordered:
photo_handle = mediaref.get_reference_handle()
photo = self.r_db.get_media_from_handle(photo_handle)
mime_type = photo.get_mime_type()
if mime_type and "image" in mime_type:
with Html("div", class_="MediaSlide") as box:
lb_content += box
lightbox += 1
image_number = "%d/%d - %s" % (lightbox,
self.max_img, photo.get_description())
box += Html("div", image_number,
class_="MediaNumber")
try:
thb_img = (
self.report.build_url_fname(
photo_handle, "img", True,
image=True) + self.ext)
fname_, ext = os.path.splitext(photo.get_path())
url_img = (self.report.build_url_fname(photo_handle,
"images",
True,
image=True) + ext)
photo_handle = mediaref.get_reference_handle() box += Html("a", href=thb_img) + (
photo = self.r_db.get_media_from_handle(photo_handle) Html("img", src=url_img,
style="width:100%")
if photo_handle in displayed: )
continue except (IOError, OSError) as msg:
mime_type = photo.get_mime_type() self.r_user.warn(_("Could not add photo to page"),
str(msg))
# get media description
descr = photo.get_description()
if mime_type:
try:
# create thumbnail url
# extension needs to be added as it is not
# already there
url = (self.report.build_url_fname(photo_handle,
"thumb",
True,
image=True) +
".png")
# begin hyperlink
toggle += self.media_link(photo_handle, url,
descr,
uplink=self.uplink,
usedescr=True)
except (IOError, OSError) as msg:
self.r_user.warn(_("Could not add photo to page"),
str(msg))
else:
try:
# begin hyperlink
toggle += self.doc_link(photo_handle, descr,
uplink=self.uplink)
except (IOError, OSError) as msg:
self.r_user.warn(_("Could not add photo to page"),
str(msg))
displayed.append(photo_handle) displayed.append(photo_handle)
if lightboxes_1:
toggle += lightboxes_1
toggle += lightboxes_2
if lightbox < len(photolist):
toggle += Html("h3",
self._("Other media: vidéos, pdfs..."),
inline=True)
if medias:
toggle += medias
# add fullclear for proper styling # add fullclear for proper styling
section += FULLCLEAR section += FULLCLEAR

View File

@ -395,7 +395,6 @@ class EventPages(BasePage):
ldatec = event.get_change_time() ldatec = event.get_change_time()
event_media_list = event.get_media_list() event_media_list = event.get_media_list()
self.uplink = True self.uplink = True
subdirs = True subdirs = True
evt_type = self._(event.get_type().xml_str()) evt_type = self._(event.get_type().xml_str())
@ -404,7 +403,19 @@ class EventPages(BasePage):
output_file, sio = self.report.create_file(event_handle, "evt") output_file, sio = self.report.create_file(event_handle, "evt")
result = self.write_header(self._("Events")) result = self.write_header(self._("Events"))
eventpage, dummy_head, dummy_body, outerwrapper = result eventpage, head, dummy_body, outerwrapper = result
if event_media_list and self.create_media:
if self.the_lang and not self.usecms:
fname = "/".join(["..", "css", "lightbox.css"])
jsname = "/".join(["..", "css", "lightbox.js"])
else:
fname = "/".join(["css", "lightbox.css"])
jsname = "/".join(["css", "lightbox.js"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
url = self.report.build_url_fname(jsname, None, self.uplink)
head += Html("script", src=url, type="text/javascript", inline=True)
# start event detail division # start event detail division
with Html("div", class_="content", id="EventDetail") as eventdetail: with Html("div", class_="content", id="EventDetail") as eventdetail:

View File

@ -390,7 +390,7 @@ class FamilyPages(BasePage):
output_file, sio = self.report.create_file(family.get_handle(), "fam") output_file, sio = self.report.create_file(family.get_handle(), "fam")
result = self.write_header(family_name) result = self.write_header(family_name)
familydetailpage, dummy_head, dummy_body, outerwrapper = result familydetailpage, head, dummy_body, outerwrapper = result
# begin FamilyDetaill division # begin FamilyDetaill division
with Html("div", class_="content", with Html("div", class_="content",
@ -400,6 +400,18 @@ class FamilyPages(BasePage):
# family media list for initial thumbnail # family media list for initial thumbnail
if self.create_media: if self.create_media:
media_list = family.get_media_list() media_list = family.get_media_list()
if media_list:
if self.the_lang and not self.usecms:
fname = "/".join(["..", "css", "lightbox.css"])
jsname = "/".join(["..", "css", "lightbox.js"])
else:
fname = "/".join(["css", "lightbox.css"])
jsname = "/".join(["css", "lightbox.js"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
url = self.report.build_url_fname(jsname, None, self.uplink)
head += Html("script", src=url, type="text/javascript", inline=True)
# If Event pages are not being created, then we need to display # If Event pages are not being created, then we need to display
# the family event media here # the family event media here
if not self.inc_events: if not self.inc_events:
@ -429,7 +441,6 @@ class FamilyPages(BasePage):
relationshipdetail += families relationshipdetail += families
# display additional images as gallery # display additional images as gallery
if self.create_media and media_list:
addgallery = self.disp_add_img_as_gallery(media_list, family) addgallery = self.disp_add_img_as_gallery(media_list, family)
if addgallery: if addgallery:
relationshipdetail += addgallery relationshipdetail += addgallery

View File

@ -1150,6 +1150,12 @@ class NavWebReport(Report):
fname = CSS["behaviour"]["filename"] fname = CSS["behaviour"]["filename"]
self.copy_file(fname, "behaviour.css", "css") self.copy_file(fname, "behaviour.css", "css")
# copy lightbox style sheet and javascript
fname = CSS["lightbox"]["filename"]
self.copy_file(fname, "lightbox.css", "css")
fname = CSS["lightbox_js"]["filename"]
self.copy_file(fname, "lightbox.js", "css")
# copy Menu Layout Style Sheet if Blue or Visually is being # copy Menu Layout Style Sheet if Blue or Visually is being
# used as the stylesheet? # used as the stylesheet?
if CSS[self.css]["navigation"]: if CSS[self.css]["navigation"]:

View File

@ -510,7 +510,7 @@ class PersonPages(BasePage):
output_file, sio = self.report.create_file(person.get_handle(), "ppl") output_file, sio = self.report.create_file(person.get_handle(), "ppl")
self.uplink = True self.uplink = True
result = self.write_header(self.sort_name) result = self.write_header(self.sort_name)
indivdetpage, dummy_head, dummy_body, outerwrapper = result indivdetpage, head, dummy_body, outerwrapper = result
# begin individualdetail division # begin individualdetail division
with Html("div", class_="content", with Html("div", class_="content",
@ -587,6 +587,18 @@ class PersonPages(BasePage):
media_list += event.get_media_list() media_list += event.get_media_list()
# display additional images as gallery # display additional images as gallery
if photo_list and self.create_media:
if self.the_lang and not self.usecms:
fname = "/".join(["..", "css", "lightbox.css"])
jsname = "/".join(["..", "css", "lightbox.js"])
else:
fname = "/".join(["css", "lightbox.css"])
jsname = "/".join(["css", "lightbox.js"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
url = self.report.build_url_fname(jsname, None, self.uplink)
head += Html("script", src=url, type="text/javascript", inline=True)
sect7 = self.disp_add_img_as_gallery(media_list, person) sect7 = self.disp_add_img_as_gallery(media_list, person)
if sect7 is not None: if sect7 is not None:
individualdetail += sect7 individualdetail += sect7

View File

@ -375,7 +375,18 @@ class PlacePages(BasePage):
outerwrapper += placedetail outerwrapper += placedetail
media_list = place.get_media_list() media_list = place.get_media_list()
if self.create_media: if media_list and self.create_media:
if self.the_lang and not self.usecms:
fname = "/".join(["..", "css", "lightbox.css"])
jsname = "/".join(["..", "css", "lightbox.js"])
else:
fname = "/".join(["css", "lightbox.css"])
jsname = "/".join(["css", "lightbox.js"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
url = self.report.build_url_fname(jsname, None, self.uplink)
head += Html("script", src=url, type="text/javascript", inline=True)
thumbnail = self.disp_first_img_as_thumbnail(media_list, thumbnail = self.disp_first_img_as_thumbnail(media_list,
place) place)
if thumbnail is not None: if thumbnail is not None:

View File

@ -233,7 +233,7 @@ class SourcePages(BasePage):
self.uplink = True self.uplink = True
result = self.write_header("%s - %s" % (self._('Sources'), result = self.write_header("%s - %s" % (self._('Sources'),
self.page_title)) self.page_title))
sourcepage, dummy_head, dummy_body, outerwrapper = result sourcepage, head, dummy_body, outerwrapper = result
ldatec = 0 ldatec = 0
# begin source detail division # begin source detail division
@ -242,6 +242,17 @@ class SourcePages(BasePage):
media_list = source.get_media_list() media_list = source.get_media_list()
if self.create_media and media_list: if self.create_media and media_list:
if self.the_lang and not self.usecms:
fname = "/".join(["..", "css", "lightbox.css"])
jsname = "/".join(["..", "css", "lightbox.js"])
else:
fname = "/".join(["css", "lightbox.css"])
jsname = "/".join(["css", "lightbox.js"])
url = self.report.build_url_fname(fname, None, self.uplink)
head += Html("link", href=url, type="text/css",
media="screen", rel="stylesheet")
url = self.report.build_url_fname(jsname, None, self.uplink)
head += Html("script", src=url, type="text/javascript", inline=True)
thumbnail = self.disp_first_img_as_thumbnail(media_list, thumbnail = self.disp_first_img_as_thumbnail(media_list,
source) source)
if thumbnail is not None: if thumbnail is not None:

View File

@ -107,6 +107,14 @@ def load_on_reg(dbstate, uistate, plugin):
["behaviour", 0, "Behaviour", ["behaviour", 0, "Behaviour",
path_css('behaviour.css'), None, [], []], path_css('behaviour.css'), None, [], []],
# media lightbox style sheet
["lightbox", 0, "",
path_css('lightbox.css'), None, [], []],
# media lightbox javascript
["lightbox_js", 0, "",
path_css('lightbox.js'), None, [], []],
# NarrativeMap stylesheet/ image for NarrativeWeb place maps # NarrativeMap stylesheet/ image for NarrativeWeb place maps
["NarrativeMaps", 0, "", ["NarrativeMaps", 0, "",
path_css("narrative-maps.css"), None, [], []], path_css("narrative-maps.css"), None, [], []],