Commit 52ad202f authored by Paolo Benvenuto's avatar Paolo Benvenuto

Merge branch 'release/v3.7beta3'

parents 5031deea 3b69ed07
# MyPhotoShare v. 3.7beta (January 22, 2019)
# MyPhotoShare v. 3.7beta3 (February 2, 2019)
### A Web Photo Gallery Done Right via Static JSON & Dynamic Javascript
#### by Jason A. Donenfeld (<Jason@zx2c4.com>), Jerome Charaoui (jerome@riseup.net) Joachim Tingvold (joachim@tingvold.com), Paolo Benvenuto (<paolobenve@gmail.com>), Pierre Métras (<p.metras@videotron.ca>)
......@@ -8,7 +8,7 @@
It permits browsing the media by folder, date and geotags.
Whenever geotagged photos are found, a map with the photo(s) position can be shown using OpenLayers, and clicking the photo markers the photo thumbnails for that point are shown.
Whenever geotagged photos are found, a map with the photo(s) position can be shown using Leaflet, and clicking the photo markers the photo thumbnails for that point are shown.
Content (albums and media files) can be shared over some popular social plaforms.
......@@ -42,6 +42,7 @@ Content (albums and media files) can be shared over some popular social plaforms
- [x] Media and folders can be sorted ascending/descending, by date or by name.
- [x] Media can be searched by file/album name, title, description, tags; search may be whole word or inside words, considering accents and capitals or not, in current album (in virtual albums too) or in the whole tree.
- [x] Thumbnails can be square (cropped, with optional face detection by [OpenCV](https://opencv.org/)) or full-content.
- [x] Maps can be generated (in a popup, thanks to Leaflet) showing photos positions; the points are clickable and the thumbnails are shown; more photos can be added (with shift-click) and removed (with ctl-click) from the popup.
### For Photographers
......
......@@ -6,16 +6,16 @@ CONF="$1"
if [ -z "$CONF" ]; then
# The script must be launched with the user's config file
echo
echo "Usage: ./$0 MYPHOTOSHARE_CONFIG_FILE"
echo
echo "Quitting"
( >&2 echo )
( >&2 echo "Usage: ./$0 MYPHOTOSHARE_CONFIG_FILE" )
( >&2 echo )
( >&2 echo "Quitting" )
exit 1
elif [ ! -f "$CONF" ]; then
echo
echo "Error: file '$CONF' does not exist"
echo
echo "Quitting"
( >&2 echo )
( >&2 echo "Error: file '$CONF' does not exist" )
( >&2 echo )
( >&2 echo "Quitting" )
exit 1
fi
......@@ -26,11 +26,11 @@ if [ ! -e "$DEFAULT_CONF" ]; then
DEFAULT_CONF="$PROJECT_DIR/myphotoshare.conf.defaults"
fi
if [ ! -e "$DEFAULT_CONF" ]; then
echo
echo "Can't find default config file 'myphotoshare.conf.defaults'."
echo "Run $0 from MyPhotoShare root directory."
echo
echo "Quitting"
( >&2 echo )
( >&2 echo "Can't find default config file 'myphotoshare.conf.defaults'." )
( >&2 echo "Run $0 from MyPhotoShare root directory." )
( >&2 echo )
( >&2 echo "Quitting" )
exit 1
fi
......@@ -55,39 +55,39 @@ case $MINIFY_JS in
web_service)
curl https://javascript-minifier.com/ > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'curl' not installed or 'https://javascript-minifier.com/' down"
echo "Aborting..."
( >&2 echo "'curl' not installed or 'https://javascript-minifier.com/' down" )
( >&2 echo "Aborting..." )
exit 1
fi
;;
jsmin2)
python2 -m jsmin > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'jsmin' for Python2 is not installed. Look for package 'python-jsmin' or 'https://github.com/tikitu/jsmin'"
echo "Aborting..."
( >&2 echo "'jsmin' for Python2 is not installed. Look for package 'python-jsmin' or 'https://github.com/tikitu/jsmin'" )
( >&2 echo "Aborting..." )
exit 1
fi
;;
jsmin3)
python3 -m jsmin > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'jsmin' for Python3 is not installed. Look for package 'python3-jsmin' or 'https://github.com/tikitu/jsmin'"
echo "Aborting..."
( >&2 echo "'jsmin' for Python3 is not installed. Look for package 'python3-jsmin' or 'https://github.com/tikitu/jsmin'" )
( >&2 echo "Aborting..." )
exit 1
fi
;;
uglifyjs)
buble -v > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'buble' is not installed and is required for using 'uglifyjs' minifier."
echo "Look for package 'node-buble' or 'https://github.com/Rich-Harris/buble'"
echo "Aborting..."
( >&2 echo "'buble' is not installed and is required for using 'uglifyjs' minifier." )
( >&2 echo "Look for package 'node-buble' or 'https://github.com/Rich-Harris/buble'" )
( >&2 echo "Aborting..." )
exit 1
fi
uglifyjs -V > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'uglifyjs' is not installed. Look for package 'node-uglifyjs' or 'http://lisperator.net/uglifyjs/'"
echo "Aborting..."
( >&2 echo "'uglifyjs' is not installed. Look for package 'node-uglifyjs' or 'http://lisperator.net/uglifyjs/'" )
( >&2 echo "Aborting..." )
exit 1
fi
esac
......@@ -96,16 +96,16 @@ case $MINIFY_CSS in
web_service)
curl https://cssminifier.com/ > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'curl' not installed or 'https://cssminifier.com/' down"
echo "Aborting..."
( >&2 echo "'curl' not installed or 'https://cssminifier.com/' down" )
( >&2 echo "Aborting..." )
exit 1
fi
;;
cssmin)
cssmin -h > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "'cssmin' is not installed. Look for package 'cssmin' or 'https://github.com/zacharyvoase/cssmin'"
echo "Aborting..."
( >&2 echo "'cssmin' is not installed. Look for package 'cssmin' or 'https://github.com/zacharyvoase/cssmin'" )
( >&2 echo "Aborting..." )
exit 1
fi
;;
......@@ -119,7 +119,7 @@ echo
CAT_LIST=""
rm -f *.min.js
if [ $? -ne 0 ]; then
echo "Can't write files. Aborting..."
( >&2 echo "Can't write files. Aborting..." )
exit 1
fi
while read jsfile; do
......@@ -210,7 +210,7 @@ echo == Minifying css files in css directory ==
echo
rm -f *.min.css
if [ $? -ne 0 ]; then
echo "Can't write files. Aborting..."
( >&2 echo "Can't write files. Aborting..." )
exit 1
fi
ls -1 *.css | grep -Ev "min.css$" | while read cssfile; do
......@@ -226,8 +226,8 @@ ls -1 *.css | grep -Ev "min.css$" | while read cssfile; do
;;
*)
echo "Unsupported CSS minifier: $MINIFY_CSS. Check option 'css_minifier' in '$CONF'"
echo "Doing nothing on file $cssfile"
( >&2 echo "Unsupported CSS minifier: $MINIFY_CSS. Check option 'css_minifier' in '$CONF'" )
( >&2 echo "Doing nothing on file $cssfile" )
esac
done
......
......@@ -2,35 +2,79 @@
###########
# Create a default MyPhotoShare 'album.ini' file based on media content files in a directory.
# Append new media sections if 'album.ini' already exists.
# Name of metadata file (default 'album.ini') is read from config file (default /etc/myphotoshare/myphotoshare.conf)
# All media file extensions that will be considered
# jpg,jpeg,JPG,JPEG,mp4,avi,MP4,AVI
# jpg,jpeg,JPG,JPEG,mp4,avi,MP4,AVI,MOV,tiff,TIF
print_usage()
{
echo "Usage: $0 [-i METADATA_FILENAME] FOLDER"
echo "Create template of 'album.ini' file in 'FOLDER' based on its media content."
echo "When the file 'album.ini' already exists in 'FOLDER', new media are added to the file."
echo
echo "Options:"
echo " -i: Define the filename used to store user-defined metadata instead of default 'album.ini'"
echo
echo "Example:"
echo " $0 -i .album.ini ~/Pictures/vacations"
echo " Create hidden file '.album.ini' in '~/Pictures/vacations"
( >&2 echo "Usage: $0 [ -c CONFIG_FILE ] FOLDER" )
( >&2 echo "Create template of '$ALBUM_INI' file in 'FOLDER' based on its media content." )
( >&2 echo "When the file '$ALBUM_INI' already exists in 'FOLDER', new media are added to the file." )
( >&2 echo "Existing metadata is not touched." )
( >&2 echo "The name of the metadata file '$ALBUM_INI' is taken from configuration file CONFIG_FILE." )
( >&2 echo )
( >&2 echo "Options:" )
( >&2 echo " -c CONFIG_FILE: Define path and name of the configuration file else" )
( >&2 echo " will use '/etc/myphotoshare/myphotoshare.conf'." )
( >&2 echo )
( >&2 echo "Example:" )
( >&2 echo " $0 ~/Pictures/vacations" )
( >&2 echo " Create file '$ALBUM_INI' in '~/Pictures/vacations' with default metadata for all" )
( >&2 echo " media contained that folder. '$ALBUM_INI' name is read from" )
( >&2 echo " '/etc/myphotoshare/myphotoshare.conf'." )
}
# Process options
ALBUM_INI="album.ini"
# Set $CONF with configuration filename
set_config()
{
# Already defined and valid: we stop there
if [ -f "$CONF" ]; then
return
fi
# Try to use parameter
if [ -f "$1" ]; then
CONF="$1"
fi
# Else use defauly
if [ -z "$CONF" ]; then
CONF="/etc/myphotoshare/myphotoshare.conf"
fi
# Stop if nothing worked
if [ ! -f "$CONF" ]; then
( >&2 echo "Error: Configuration file '$CONF' does not exist." )
exit 1
fi
}
while getopts "i:" option; do
# Get metadata filename from configuration and set $ALBUM_INI
set_album_ini()
{
# Get metadata filename from configuration
if [ -f "$CONF" ]; then
ALBUM_INI="$(sed -nr 's/^\s*metadata_filename\s*=\s*((\w|\.)+)\s*.*$/\1/p' "$CONF")"
fi
# If not set, use defaults
if [ -z "$ALBUM_INI" ]; then
ALBUM_INI="album.ini"
fi
}
# Process options
while getopts "c:" option; do
case $option in
i)
ALBUM_INI="$OPTARG"
c)
set_config "$OPTARG"
;;
\?)
set_config
set_album_ini
print_usage
exit 1
;;
......@@ -41,6 +85,9 @@ shift $((OPTIND-1))
# Process parameters
DIR=${1%/}
set_config
set_album_ini
if [ -z "$DIR" ]; then
print_usage
exit 1
......@@ -51,6 +98,7 @@ fi
if [ ! -f "$DIR/$ALBUM_INI" ]; then
echo "Create file '$DIR/$ALBUM_INI'."
echo "# User defined metadata for MyPhotoShare" > "$DIR/$ALBUM_INI"
echo "########################################" >> "$DIR/$ALBUM_INI"
echo "# Possible metadata:" >> "$DIR/$ALBUM_INI"
......@@ -90,13 +138,12 @@ if [ $SECTION_EXISTS -eq 0 ]; then
echo "#tags = " >> "$DIR/$ALBUM_INI"
echo >> "$DIR/$ALBUM_INI"
echo >> "$DIR/$ALBUM_INI"
((SECTION_COUNT+=1))
fi
# Loop on album content
SAVEIFS="$IFS"
IFS=$(echo -en "\n\b")
for media in $(ls "$DIR"/*.{jpg,jpeg,JPG,JPEG,mp4,avi,MP4,AVI,MOV} 2> /dev/null); do
for media in $(ls "$DIR"/*.{jpg,jpeg,JPG,JPEG,mp4,avi,MP4,AVI,MOV,tiff,TIF} 2> /dev/null); do
SECTION=${media##*/}
TITLE=${SECTION%.*}
SECTION_EXISTS=$(grep -c "\[$SECTION\]" "$DIR/$ALBUM_INI")
......
### version 3.7beta (January 22, 2019)
### version 3.7beta3 (January 22, 2019)
* A position icon is added to album titles: opens an OpenLayers map with the markers for all the media in the album and subalbums
* A position icon is added to album titles: opens an Leaflet map with the markers for all the media in the album and subalbums
* The markers are clickable and they show the clickable thumbnails for that position
* The OpenLayers map is used for the single media too
* All the subalbum names in album view have the position icon that opens the OpenLayers map
* The Leaflet map is used for the single media too
* All the subalbum names in album view have the position icon that opens the Leaflet map
* Removed unnecessary options:
* `map_service`: the js app doesn't use any more those external tools
* `map_zoom_levels`
* Option`photo_map_zoom_level` removed and replaced by `photo_map_size`: size in meters of the map for a single photo
* New option `default_map_popup_position`
* More photos can be added (with shift-click) and removed (with ctl-click) from the popup
* Albums' metadata filename specification in config file (album.ini by default) in now used in `make_album_ini.sh`
### version 3.6.7 (January 14, 2019)
......
# Known issues
Updated: 2018-02-28
Updated: 2019-01-30
#### Issues related to previous scanner versions
......@@ -53,3 +53,15 @@ Due to a bug in PIL `_getexif()`, since version 3.4beta9 `exifread` (`python3-e
#### Bad dragging of enlarged photos
If a photo is enlarged, dragging works, but in a very rough manner.
#### Slow web site
If there are many images in the album (say > 1000) you may experience slowlyness in the web site. This is expecially true when the photos are geotagged.
You can speed up the site enabling `json` compression in your web server. On `debian`'s `apache2` this can be easily achieved adding the following line in the proper place to the file `/etc/apache2/mods-enabled/deflate.conf`:
```apache
AddOutputFilterByType DEFLATE application/json
```
For other web servers/distributions a solution can be found easily.
......@@ -255,6 +255,10 @@ big_virtual_folders_threshold = 999
# The map size in meters of the map for a single photo
photo_map_size = 100
# default map popup position
# allowed values in ['N', 'NE', 'E', 'SE', 'S', 'SO', 'O', 'NO'] (see options.py)
default_map_popup_position = SE
# The maximum number of search album that will be loaded
# A value of 200 seems acceptable on a pc, 100 is more sure
max_search_album_number = 100
......
......@@ -55,6 +55,7 @@ thumbnail_types_and_sizes_list = None
config['cv2_installed'] = True
face_cascade = None
eye_cascade = None
config['available_map_popup_positions'] = ['SE', 'NW' ]
# set this variable to a new value (previously was a number, now it may include letters) whenever the json files structure changes, it can be the app version
# json_version = 0 is debug mode: json files are always considered invalid
......@@ -63,7 +64,9 @@ eye_cascade = None
# json_version = 3 since geotag managing is optional
# json_version = 3.4 since search feature added
# json_version = 3.6.4 since changed wrong album/media attributes
json_version = "3.7beta1"
# json_version = 3.7beta1 since added positions_and_media_in_tree to every json file
# json_version = 3.7beta2 since mvoed positions_and_media_in_tree to a separate file to avoid duplication and to save download time
json_version = "3.7beta2"
def initialize_opencv():
global face_cascade, eye_cascade
......
......@@ -158,6 +158,10 @@ class Album(object):
def json_file(self):
return self.cache_base + ".json"
@property
def positions_json_file(self):
return self.cache_base + ".positions.json"
@property
def subdir(self):
return self._subdir
......@@ -269,6 +273,10 @@ class Album(object):
if os.path.exists(json_file_with_path) and not os.access(json_file_with_path, os.W_OK):
message("FATAL ERROR", json_file_with_path + " not writable, quitting", 0)
sys.exit(-97)
json_positions_file_with_path = os.path.join(Options.config['cache_path'], self.positions_json_file)
if os.path.exists(json_positions_file_with_path) and not os.access(json_positions_file_with_path, os.W_OK):
message("FATAL ERROR", json_positions_file_with_path + " not writable, quitting", 0)
sys.exit(-97)
message("sorting album...", self.absolute_path, 5)
self.sort_subalbums_and_media()
indented_message("album sorted", "", 4)
......@@ -276,6 +284,10 @@ class Album(object):
with open(json_file_with_path, 'w') as filepath:
json.dump(self, filepath, cls=PhotoAlbumEncoder)
indented_message("album saved", "", 3)
message("saving positions album...", "", 5)
with open(json_positions_file_with_path, 'w') as filepath:
json.dump(self.positions_and_media_in_tree, filepath, cls=PhotoAlbumEncoder)
indented_message("positions album saved", "", 3)
@staticmethod
def from_cache(path, album_cache_base):
......@@ -337,8 +349,9 @@ class Album(object):
"path": path_to_dict,
"cacheBase": subalbum.cache_base,
"date": subalbum.date_string,
"numMediaInSubTree": subalbum.num_media_in_sub_tree,
"positionsAndMediaInTree": subalbum.positions_and_media_in_tree
# "positionsAndMediaInTree": subalbum.positions_and_media_in_tree,
"numPositionsInTree": len(subalbum.positions_and_media_in_tree),
"numMediaInSubTree": subalbum.num_media_in_sub_tree
}
if hasattr(subalbum, "center"):
sub_dict["center"] = subalbum.center
......@@ -408,7 +421,8 @@ class Album(object):
"physicalPath": path_without_folders_marker,
"numMediaInSubTree": self.num_media_in_sub_tree,
"numMediaInAlbum": self.num_media_in_album,
"positionsAndMediaInTree": self.positions_and_media_in_tree,
"numPositionsInTree": len(self.positions_and_media_in_tree),
# "positionsAndMediaInTree": self.positions_and_media_in_tree,
"jsonVersion": Options.json_version
}
if hasattr(self, "center"):
......@@ -417,6 +431,8 @@ class Album(object):
dictionary["name"] = self.name
if hasattr(self, "alt_name"):
dictionary["altName"] = self.alt_name
if self.cache_base == Options.config['folders_string']:
dictionary["numPoints"] = len(self.positions_and_media_in_tree)
if self.parent is not None:
dictionary["parentCacheBase"] = self.parent.cache_base
......
......@@ -211,6 +211,12 @@ class TreeWalker:
month_album.num_media_in_sub_tree += 1
year_album.add_media(single_media)
year_album.num_media_in_sub_tree += 1
if single_media.has_gps_data:
day_album.positions_and_media_in_tree = self.add_media_to_position(day_album.positions_and_media_in_tree, single_media, Options.config['by_date_string'])
month_album.positions_and_media_in_tree = self.add_media_to_position(month_album.positions_and_media_in_tree, single_media, Options.config['by_date_string'])
year_album.positions_and_media_in_tree = self.add_media_to_position(year_album.positions_and_media_in_tree, single_media, Options.config['by_date_string'])
by_date_album.positions_and_media_in_tree = self.add_media_to_position(by_date_album.positions_and_media_in_tree, single_media, Options.config['by_date_string'])
by_date_album.add_media(single_media)
by_date_album.num_media_in_sub_tree += 1
single_media_date = max(single_media.datetime_file, single_media.datetime_dir)
......@@ -243,15 +249,20 @@ class TreeWalker:
back_level()
return by_date_album
def add_media_to_position(self, positions, media):
def add_media_to_position(self, positions, media, type_string):
# adds the media position and name to the positions list received as second argument
if type_string == Options.config['folders_string']:
media_album_cache_base = media.album.cache_base
elif type_string == Options.config['by_date_string']:
media_album_cache_base = media.day_album_cache_base
elif type_string == Options.config['by_gps_string']:
media_album_cache_base = media.gps_album_cache_base
position = {
'long': media.longitude,
'lat' : media.latitude,
'mediaNameList': [{
'name': media.media_path,
'cacheBase': media.cache_base,
'albumCacheBase': media.album.cache_base
'albumCacheBase': media_album_cache_base
}]
}
positions = self.add_position_to_positions(positions, position)
......@@ -300,6 +311,7 @@ class TreeWalker:
word_max_file_date = None
by_search_album.add_album(word_album)
for single_media in media_and_album_words["media_words"]:
# word_album.positions_and_media_in_tree = self.add_media_to_position(word_album.positions_and_media_in_tree, single_media)
word_album.add_media(single_media)
word_album.num_media_in_sub_tree += 1
word_album.num_media_in_album += 1
......@@ -477,13 +489,17 @@ class TreeWalker:
cluster[j].gps_path = remove_album_path(place_path)
cluster[j].place_name = place_name
cluster[j].alt_place_name = alt_place_name
place_album.positions_and_media_in_tree = self.add_media_to_position(place_album.positions_and_media_in_tree, single_media, Options.config['by_gps_string'])
place_album.add_media(single_media)
place_album.num_media_in_sub_tree += 1
place_album.num_media_in_album += 1
region_album.positions_and_media_in_tree = self.add_media_to_position(region_album.positions_and_media_in_tree, single_media, Options.config['by_gps_string'])
region_album.add_media(single_media)
region_album.num_media_in_sub_tree += 1
country_album.positions_and_media_in_tree = self.add_media_to_position(country_album.positions_and_media_in_tree, single_media, Options.config['by_gps_string'])
country_album.add_media(single_media)
country_album.num_media_in_sub_tree += 1
by_geonames_album.positions_and_media_in_tree = self.add_media_to_position(by_geonames_album.positions_and_media_in_tree, single_media, Options.config['by_gps_string'])
by_geonames_album.add_media(single_media)
by_geonames_album.num_media_in_sub_tree += 1
......@@ -712,12 +728,12 @@ class TreeWalker:
if not os.access(absolute_path, os.R_OK | os.X_OK):
message("access denied to directory", os.path.basename(absolute_path), 1)
back_level()
return [None, 0, None]
return [None, 0, [], None]
listdir = os.listdir(absolute_path)
if Options.config['exclude_tree_marker'] in listdir:
indented_message("excluded with subfolders by marker file", Options.config['exclude_tree_marker'], 4)
back_level()
return [None, 0, None]
return [None, 0, [], None]
skip_files = False
if Options.config['exclude_files_marker'] in listdir:
next_level()
......@@ -1012,7 +1028,7 @@ class TreeWalker:
if media.is_valid:
album.num_media_in_sub_tree += 1
if media.has_gps_data:
album.positions_and_media_in_tree = self.add_media_to_position(album.positions_and_media_in_tree, media)
album.positions_and_media_in_tree = self.add_media_to_position(album.positions_and_media_in_tree, media, Options.config['folders_string'])
album.num_media_in_album += 1
if media.is_video:
num_video_in_dir += 1
......@@ -1306,6 +1322,7 @@ class TreeWalker:
for album in self.all_albums:
self.all_json_files.append(album.json_file)
self.all_json_files.append(album.positions_json_file)
for media in self.all_media:
album_subdir = media.album.subdir
for entry in media.image_caches:
......
This diff is collapsed.
......@@ -18,12 +18,13 @@
top:0;
width:100%;
z-index:10000;
color: gray;
}
@media (max-height: 700px) {
.map-container{
height: unset !important;
width: 95%;
top: 5%;
}
}
.map-container .helper{
......@@ -50,7 +51,8 @@
height: 99%; */
color: black !important;
}
.map-close-button {
.map-close-button, #mapdiv .leaflet-popup-close-button, .popup-mover {
background-color: #fff;
border: 3px solid #999;
border-radius: 50px;
......@@ -59,125 +61,68 @@
font-family: arial;
font-weight: bold;
position: absolute;
top: -20px;
right: -20px;
font-size: 25px;
font-size: 0;
line-height: 30px;
width: 30px;
height: 30px;
text-align: center;
}
.map-close-button:hover {
background-color: #ccc;
color: white;
z-index: 1;
}
#mapdiv {
height: 100%;
.map-close-button, #mapdiv .leaflet-popup-close-button {
top: -20px;
}
#mapdiv .ol-overlaycontainer-stopevent ul {
list-style-type: none;
}
#mapdiv .ol-overlaycontainer-stopevent ul li {
font-size: 80%;
list-style-type: none;
}
#mapdiv .ol-overlaycontainer-stopevent ul li a {
color: black;
.map-close-button::after, #mapdiv .leaflet-popup-close-button::after {
content: "✖";
}
#mapdiv .ol-overlaycontainer-stopevent {
/* position: absolute; */
bottom: 1%;
right: 1%;
.map-close-button::after, #mapdiv .leaflet-popup-close-button::after, .popup-mover::after {
color: gray;
font-size: 25px;
}
/* Popup box END */
.ol-rotate, .ol-rotate-reset, .ol-attribution button {
display: none;
}
.ol-scale-line {
background: rgba(0,60,136,0.3);
border-radius: 4px;
bottom: 8px;
left: 8px;
padding: 2px;
/* position: absolute; */
}
.ol-scale-line-inner {
border: 1px solid #eee;
border-top: none;
color: #eee;
font-size: 10px;
text-align: center;
margin: 1px;
will-change: contents, width;
margin: auto;
}
.ol-attribution {
text-align: center;
bottom: .5em;
right: .5em;
/* max-width: calc(100% - 1.3em); */
}
.ol-attribution ul {
margin: 0;
padding: 0 .5em;
font-size: .7rem;
line-height: 1.375em;
color: #000;
text-shadow: 0 0 2px #fff;
}
.ol-attribution li {
display: inline;
list-style: none;
line-height: inherit;
}
.ol-attribution li:not(:last-child):after {
content: " ";
}
.ol-attribution img {
max-height: 2em;
max-width: inherit;
vertical-align: middle;
}
.ol-attribution ul, .ol-attribution button {
display: inline-block;
.popup-mover {
bottom: -20px;
left: -20px
}
.ol-attribution.ol-collapsed ul {
display: none;
}
.ol-attribution:not(.ol-collapsed) {
/* background: rgba(255,255,255,0.8); */
.SE .popup-mover::after {
content: "⭦";
}
.ol-attribution.ol-uncollapsible {
bottom: 0;
right: 0;
border-radius: 4px 0 0;
height: 1.1em;
line-height: 1em;
.NW .popup-mover::after {
content: "⭨";
}
.ol-attribution.ol-uncollapsible img {
margin-top: -.2em;
max-height: 1.6em;
.map-close-button:hover, a.leaflet-popup-close-button:hover, a.popup-mover:hover {
background-color: #ccc;
color: gray;
}
.ol-attribution.ol-uncollapsible button {
display: none;
#mapdiv {
height: 100%;
cursor: pointer;
}
.ol-zoom {
width: 30px;
margin: auto;
/* Popup box END */
.leaflet-popup-content .media-caption {
color: black;
}
.ol-zoom-in, .ol-zoom-out {
padding: 2px;
width: 24px;
.leaflet-popup-content {
overflow-y: auto;
}
.leaflet-popup-content .thumb-and-caption-container {
display: inline-block;
vertical-align: top;
}
.ol-popup {
/* .leaflet-popup {
position: absolute;
background-color: white;
-webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
......@@ -185,46 +130,56 @@
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
/* min-width: 280px; */
}
.ol-popup:after, .ol-popup:before {
top: 100%;
border: solid transparent;