Compare commits
18 commits
89eed9f53c
...
76e8d7f185
Author | SHA1 | Date | |
---|---|---|---|
![]() |
76e8d7f185 | ||
![]() |
be83eddbd0 | ||
![]() |
4df201107e | ||
![]() |
60f16cbc76 | ||
![]() |
2fa88c36f8 | ||
![]() |
47c5c0a2f0 | ||
![]() |
e548ec60f1 | ||
![]() |
190acbfaf0 | ||
![]() |
1370b1a0e8 | ||
![]() |
aa75b323c8 | ||
![]() |
1c00545095 | ||
![]() |
54a3aae912 | ||
![]() |
9d4069d9ae | ||
![]() |
167bab70c5 | ||
![]() |
d3ed46356d | ||
![]() |
5517e1f437 | ||
![]() |
91b7b93bf4 | ||
![]() |
a7837aa54a |
|
@ -1,5 +1,5 @@
|
|||
# Force rtfd to use a recent version of mkdocs
|
||||
mkdocs==1.6.1
|
||||
pymdown-extensions==10.14.3
|
||||
mkdocs-material==9.6.7
|
||||
mkdocs-material==9.6.9
|
||||
mkdocs-static-i18n==1.3.0
|
||||
|
|
|
@ -323,7 +323,10 @@ CREATE EXTENSION btree_gin;
|
|||
ALTER TEXT SEARCH CONFIGURATION umapdict ALTER MAPPING FOR hword, hword_part, word WITH unaccent, simple;
|
||||
|
||||
# Now create the index
|
||||
CREATE INDEX IF NOT EXISTS search_idx ON umap_map USING GIN(to_tsvector('umapdict', COALESCE(name, ''::character varying)::text), share_status);
|
||||
CREATE INDEX IF NOT EXISTS search_idx ON umap_map USING GIN(to_tsvector('umapdict', COALESCE(name, ''::character varying)::text), share_status, tags);
|
||||
|
||||
# You should also create an index for tag filtering:
|
||||
CREATE INDEX IF NOT EXISTS tags_idx ON umap_map USING GIN(share_status, tags);
|
||||
```
|
||||
|
||||
Then set:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Force rtfd to use a recent version of mkdocs
|
||||
mkdocs==1.6.1
|
||||
pymdown-extensions==10.14.3
|
||||
mkdocs-material==9.6.7
|
||||
mkdocs-material==9.6.9
|
||||
mkdocs-static-i18n==1.3.0
|
||||
|
|
|
@ -33,7 +33,7 @@ dependencies = [
|
|||
"django-environ==0.12.0",
|
||||
"django-probes==1.7.0",
|
||||
"Pillow==11.1.0",
|
||||
"psycopg==3.2.5",
|
||||
"psycopg==3.2.6",
|
||||
"requests==2.32.3",
|
||||
"rcssmin==1.2.1",
|
||||
"rjsmin==1.2.4",
|
||||
|
@ -44,10 +44,10 @@ dependencies = [
|
|||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"hatch==1.14.0",
|
||||
"ruff==0.9.10",
|
||||
"ruff==0.11.2",
|
||||
"djlint==1.36.4",
|
||||
"mkdocs==1.6.1",
|
||||
"mkdocs-material==9.6.7",
|
||||
"mkdocs-material==9.6.9",
|
||||
"mkdocs-static-i18n==1.3.0",
|
||||
"vermin==1.6.0",
|
||||
"pymdown-extensions==10.14.3",
|
||||
|
|
|
@ -14,6 +14,7 @@ def settings(request):
|
|||
"UMAP_DEMO_SITE": djsettings.UMAP_DEMO_SITE,
|
||||
"UMAP_HOST_INFOS": djsettings.UMAP_HOST_INFOS,
|
||||
"UMAP_ALLOW_EDIT_PROFILE": djsettings.UMAP_ALLOW_EDIT_PROFILE,
|
||||
"UMAP_TAGS": djsettings.UMAP_TAGS,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ from django.contrib.auth import get_user_model
|
|||
from django.contrib.gis.geos import Point
|
||||
from django.forms.utils import ErrorList
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import DataLayer, Map, Team
|
||||
|
||||
|
@ -92,7 +91,7 @@ class MapSettingsForm(forms.ModelForm):
|
|||
return self.cleaned_data["center"]
|
||||
|
||||
class Meta:
|
||||
fields = ("settings", "name", "center", "slug")
|
||||
fields = ("settings", "name", "center", "slug", "tags")
|
||||
model = Map
|
||||
|
||||
|
||||
|
|
23
umap/migrations/0027_map_tags.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.1.6 on 2025-02-26 16:18
|
||||
|
||||
import django.contrib.postgres.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("umap", "0026_datalayer_modified_at_datalayer_share_status"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="map",
|
||||
name="tags",
|
||||
field=django.contrib.postgres.fields.ArrayField(
|
||||
base_field=models.CharField(max_length=200),
|
||||
blank=True,
|
||||
default=list,
|
||||
size=None,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -4,6 +4,7 @@ import uuid
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.db import models
|
||||
from django.contrib.postgres.fields import ArrayField
|
||||
from django.core.files.base import File
|
||||
from django.core.files.storage import storages
|
||||
from django.core.signing import Signer
|
||||
|
@ -236,6 +237,7 @@ class Map(NamedModel):
|
|||
settings = models.JSONField(
|
||||
blank=True, null=True, verbose_name=_("settings"), default=dict
|
||||
)
|
||||
tags = ArrayField(models.CharField(max_length=200), blank=True, default=list)
|
||||
|
||||
objects = models.Manager()
|
||||
public = PublicManager()
|
||||
|
@ -420,7 +422,8 @@ class Map(NamedModel):
|
|||
return {
|
||||
"iconUrl": {
|
||||
"default": "%sumap/img/marker.svg" % settings.STATIC_URL,
|
||||
}
|
||||
},
|
||||
"tags": {"choices": settings.UMAP_TAGS},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from email.utils import parseaddr
|
|||
|
||||
import environ
|
||||
from django.conf.locale import LANG_INFO
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import umap as project_module
|
||||
|
||||
|
@ -290,6 +291,25 @@ UMAP_HOME_FEED = "latest"
|
|||
UMAP_IMPORTERS = {}
|
||||
UMAP_HOST_INFOS = {}
|
||||
UMAP_LABEL_KEYS = ["name", "title"]
|
||||
UMAP_TAGS = (
|
||||
("arts", _("Art and Culture")),
|
||||
("cycling", _("Cycling")),
|
||||
("business", _("Business")),
|
||||
("environment", _("Environment")),
|
||||
("education", _("Education")),
|
||||
("food", _("Food and Agriculture")),
|
||||
("geopolitics", _("Geopolitics")),
|
||||
("health", _("Health")),
|
||||
("hiking", _("Hiking")),
|
||||
("history", _("History")),
|
||||
("public", _("Public sector")),
|
||||
("science", _("Science")),
|
||||
("shopping", _("Shopping")),
|
||||
("sport", _("Sport and Leisure")),
|
||||
("travel", _("Travel")),
|
||||
("transports", _("Transports")),
|
||||
("tourism", _("Tourism")),
|
||||
)
|
||||
|
||||
UMAP_READONLY = env("UMAP_READONLY", default=False)
|
||||
UMAP_GZIP = True
|
||||
|
|
|
@ -202,3 +202,15 @@ dt {
|
|||
height: 100vh;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.table-scrollable {
|
||||
background-image: linear-gradient(to right, var(--background-color), var(--background-color)),
|
||||
linear-gradient(to right, var(--background-color), var(--background-color)),
|
||||
linear-gradient(to right, rgba(0, 0, 20, .50), rgba(255, 255, 255, 0)),
|
||||
linear-gradient(to left, rgba(0, 0, 20, .50), rgba(255, 255, 255, 0));
|
||||
background-position: left center, right center, left center, right center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 20px 100%, 20px 100%, 10px 100%, 10px 100%;
|
||||
background-attachment: local, local, scroll, scroll;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
z-index: var(--zindex-dialog);
|
||||
margin: auto;
|
||||
margin-top: 100px;
|
||||
width: 40vw;
|
||||
width: var(--dialog-width);
|
||||
max-width: 100vw;
|
||||
max-height: 50vh;
|
||||
padding: 20px;
|
||||
|
|
|
@ -55,3 +55,10 @@
|
|||
.importers ul .datasets:before {
|
||||
background-image: url(../img/importers/datasets.svg);
|
||||
}
|
||||
.importer.banfr h3:before,
|
||||
.importers ul .banfr:before {
|
||||
background-image: url(../img/importers/banfr.svg);
|
||||
}
|
||||
.importer table {
|
||||
width: calc(var(--dialog-width) - 2 * var(--box-margin));
|
||||
}
|
||||
|
|
1
umap/static/umap/img/importers/banfr.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="none"><path fill="#bebebe" d="M40 0H10C4.477 0 0 4.477 0 10v30c0 5.523 4.477 10 10 10h30c5.523 0 10-4.477 10-10V10c0-5.523-4.477-10-10-10z" style="stroke-width:1"/><path fill="#bfbfbf" fill-opacity=".9" fill-rule="evenodd" d="M29.023 20.012v6.053a1.226 1.226 0 0 0 2.451 0v-6.053a1.226 1.226 0 0 0-2.451 0z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#E1000F" fill-opacity=".9" fill-rule="evenodd" d="m24.393 18.047 5.242 3.027a1.226 1.226 0 0 0 1.225-2.123l-5.241-3.027a1.226 1.226 0 0 0-1.226 2.123z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#000091" fill-opacity=".9" fill-rule="evenodd" d="m29.635 25.004-5.242 3.026a1.226 1.226 0 0 0 1.226 2.123l5.241-3.027a1.226 1.226 0 0 0-1.225-2.122z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#bfbfbf" fill-opacity=".9" fill-rule="evenodd" d="M20.99 26.065v-6.053a1.226 1.226 0 0 0-2.451 0v6.053a1.226 1.226 0 0 0 2.451 0z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#000091" fill-opacity=".9" fill-rule="evenodd" d="m20.377 21.074 5.242-3.027a1.226 1.226 0 0 0-1.226-2.123l-5.241 3.027a1.226 1.226 0 0 0 1.225 2.123z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#E1000F" fill-opacity=".9" fill-rule="evenodd" d="m14.934 27.326 4.84-.035a1.226 1.226 0 0 0-.018-2.452l-4.84.035a1.226 1.226 0 0 0 .018 2.452z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#bfbfbf" fill-opacity=".9" fill-rule="evenodd" d="M18.62 14.22A9.977 9.977 0 0 1 25 11.924c2.424 0 4.648.864 6.38 2.298a1.226 1.226 0 0 0 1.564-1.888A12.417 12.417 0 0 0 25 9.471a12.417 12.417 0 0 0-7.944 2.862 1.226 1.226 0 0 0 1.564 1.888z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#000091" fill-opacity=".9" fill-rule="evenodd" d="M16.022 25.565c-.622-1.309-1.035-2.553-1.035-3.628a9.998 9.998 0 0 1 3.632-7.716 1.225 1.225 0 1 0-1.563-1.888 12.443 12.443 0 0 0-4.52 9.604c0 1.385.473 2.997 1.271 4.679a1.226 1.226 0 0 0 2.215-1.051z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#E1000F" fill-opacity=".9" fill-rule="evenodd" d="M31.38 14.221a9.997 9.997 0 0 1 3.633 7.716c0 1.682-.967 3.75-2.248 5.846-3.235 5.298-8.629 10.652-8.629 10.652a1.224 1.224 0 1 0 1.728 1.737s5.62-5.586 8.993-11.11c1.551-2.541 2.607-5.087 2.607-7.125 0-3.861-1.759-7.318-4.52-9.604a1.226 1.226 0 0 0-1.564 1.888z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/><path fill="#000091" fill-opacity=".9" fill-rule="evenodd" d="M25.864 38.435s-3.874-3.85-7.03-8.252a1.227 1.227 0 0 0-1.992 1.427c3.275 4.57 7.294 8.562 7.294 8.562.48.478 1.257.475 1.734-.006a1.224 1.224 0 0 0-.006-1.731z" clip-rule="evenodd" style="fill:#323737;fill-opacity:1;stroke-width:1"/></svg>
|
After Width: | Height: | Size: 2.9 KiB |
1
umap/static/umap/img/tags/arts.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M2 1S1 1 1 2v5.158C1 8.888 1.354 11 4.5 11H5V8L2.5 9s0-2.5 2.5-2.5V5c0-.708.087-1.32.5-1.775.381-.42 1.005-1.258 2.656-.471L9 3.303V2s0-1-1-1c-.708 0-1.978 1-3 1S2.787 1 2 1zm1 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 1S6 4 6 5v5c0 2 1 4 4 4s4-2 4-4V5c0-1-1-1-1-1-.708 0-1.978 1-3 1S7.787 4 7 4zm1 2a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm4 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm-4.5 4h5s0 2.5-2.5 2.5S7.5 10 7.5 10z"/></svg>
|
After Width: | Height: | Size: 472 B |
1
umap/static/umap/img/tags/business.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M4.5 4V2a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2H12a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h1.5Zm1-2v2h4V2h-4Z"/></svg>
|
After Width: | Height: | Size: 205 B |
1
umap/static/umap/img/tags/cycling.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M10 1a1 1 0 1 0 0 2 1 1 0 0 0 0-2zM8.145 2.994a.5.5 0 0 0-.348.143l-2.64 2.5a.5.5 0 0 0 .042.763L7 7.75v2.75c-.01.676 1.01.676 1 0v-3a.5.5 0 0 0-.2-.4l-.767-.577 1.818-1.72.749.998A.5.5 0 0 0 10 6h1.5c.676.01.676-1.01 0-1h-1.25L9.5 4l-.6-.8a.5.5 0 0 0-.384-.206h-.371zM3 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6zm9 0a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM3 8a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm9 0a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/></svg>
|
After Width: | Height: | Size: 481 B |
1
umap/static/umap/img/tags/education.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M10 0v15H0V0h10zM9 1H1v13h8V1zM2.5 4h5c.5 0 .5 1 0 1h-5C2 5 2 4 2.5 4zm0 2h5c.5 0 .5 1 0 1h-5C2 7 2 6 2.5 6zm0 2h5c.5 0 .5 1 0 1h-5C2 9 2 8 2.5 8zm0 2h5c.5 0 .5 1 0 1h-5c-.5 0-.5-1 0-1zM11 13c.5.5 2.5.5 3 0 0 0-1 2-1.5 2S11 13 11 13zm0-10c0 .5 3 .5 3 0v9c0 .5-3 .5-3 0V3zm1.5-3C11 0 11 .5 11 1v1c0 .5 3 .5 3 0V1c0-.5 0-1-1.5-1z"/></svg>
|
After Width: | Height: | Size: 405 B |
1
umap/static/umap/img/tags/environment.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M2.456 8.613c-.338.598-.955 1.69.137 2.418.343.227.728.384 1.131.462.307.045.323.518-.038.507-.385-.02-2.26-.193-2.561-1.6-.156-.82.02-1.557.504-2.355l.697-1.233-1.306-.743L4.5 4v4l-1.306-.694-.738 1.307zM6.7 2.034c1.155-.628 1.823.43 2.191 1.007l.806 1.263-1.266.808L12 6.986l-.197-4.026-1.264.807-.76-1.189c-.522-.746-.904-1.297-1.835-1.545C6.307.72 5.301 2.619 5.311 2.607c-.164.287.216.54.451.21.258-.32.577-.586.938-.783zm6.594 6.187c-.088-.19-.549-.141-.419.267.131.39.184.8.157 1.21C12.939 11.01 11.684 11 11 11H9.5V9.5l-3.5 2 3.488 2.025L9.493 12H11c.89.015 1.6-.176 2.2-.713 1.2-1.061.094-3.066.094-3.066z"/></svg>
|
After Width: | Height: | Size: 695 B |
1
umap/static/umap/img/tags/food.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="m3.5 0-1 5.5c-.146.805 1.782 1.181 1.75 2L4 14c-.038 1 1 1 1 1s1.038 0 1-1l-.25-6.5c-.031-.818 1.733-1.18 1.75-2L6.5 0H6l.25 4-.75.5L5.25 0h-.5L4.5 4.5 3.75 4 4 0h-.5zM12 0c-.736 0-1.964.655-2.455 1.637C9.135 2.373 9 4.018 9 5v2.5c0 .818 1.09 1 1.5 1L10 14c-.09.996 1 1 1 1s1 0 1-1V0z"/></svg>
|
After Width: | Height: | Size: 365 B |
1
umap/static/umap/img/tags/geopolitics.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M6.65 2C5.43 2 4.48 3.38 4.11 3.82a.49.49 0 0 0-.11.32v4.4a.44.44 0 0 0 .72.36 3 3 0 0 1 1.93-1.17C8.06 7.73 8.6 9 10.07 9a5.28 5.28 0 0 0 2.73-1.09.49.49 0 0 0 .2-.4V2.45a.44.44 0 0 0-.62-.45 5.75 5.75 0 0 1-2.31 1.06C8.6 3.08 8.12 2 6.65 2zM2.5 3a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM3 4v9.48a.5.5 0 0 1-1 0V4a.5.5 0 0 1 1 0z"/></svg>
|
After Width: | Height: | Size: 400 B |
1
umap/static/umap/img/tags/health.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M7 1c-.6 0-1 .4-1 1v4H2c-.6 0-1 .4-1 1v1c0 .6.4 1 1 1h4v4c0 .6.4 1 1 1h1c.6 0 1-.4 1-1V9h4c.6 0 1-.4 1-1V7c0-.6-.4-1-1-1H9V2c0-.6-.4-1-1-1H7z"/></svg>
|
After Width: | Height: | Size: 222 B |
1
umap/static/umap/img/tags/hiking.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" baseProfile="tiny" overflow="inherit" version="1.2" viewBox="0 0 50 50"><path d="M6.43 21.55c-.22.45-.37.94-.46 1.47-.04.26-.06.53-.06.79l.19 12.13-4.2 9.02c-.14.27-.25.58-.3.9-.23 1.49.8 2.88 2.3 3.11 1.16.18 2.27-.41 2.8-1.39l4.63-9.86c.1-.23.18-.47.22-.73l.03-.37-.04-7.5 7.11 3.09 1.15 7.33a2.732 2.732 0 0 0 2.26 2.11c1.5.22 2.89-.81 3.12-2.28.04-.25.04-.51.01-.75l-1.4-8.84c-.17-.85-.74-1.59-1.53-1.96l-6.35-2.81L19.96 18l2.01 2.54c.21.23.47.42.77.54l7.65 2.23a2.14 2.14 0 0 0 2.45-1.29 2.14 2.14 0 0 0-1.18-2.8l-.11-.04-6.64-1.95-5-5.98A5.079 5.079 0 0 0 17 9.71c-2.03-.3-3.96.66-4.97 2.3l-5.56 9.57zm21.94 17.38-.48 3.63-13.16 3.19.13 2.25h32.09c1.14 0 2.06-.91 2.06-2.04l-.04-43.07-4.4-1.02-2.53 11.32-4.22 1.77-3.74 10.44 3.56 7.99-1 3.07-8.26 2.48zM19.44 9.15c2.26 0 4.1-1.83 4.1-4.08S21.7.99 19.45.99s-4.1 1.83-4.1 4.08 1.84 4.08 4.1 4.08zm-8.15.64c.31-.55.13-1.27-.43-1.59L8.88 7.05c-.57-.31-1.28-.13-1.61.43l-6.11 10.5c-.31.55-.13 1.26.43 1.59l1.99 1.14c.56.32 1.27.13 1.59-.42l6.11-10.5z"/></svg>
|
After Width: | Height: | Size: 1 KiB |
1
umap/static/umap/img/tags/history.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M7.5 0 1 3.5V4h13v-.5L7.5 0zM2 5v5l-1 1.6V13h13v-1.4L13 10V5H2zm2 1h1v5.5H4V6zm3 0h1v5.5H7V6zm3 0h1v5.5h-1V6z"/></svg>
|
After Width: | Height: | Size: 190 B |
1
umap/static/umap/img/tags/public.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M9 14v-3H6v3H1V6.5c0-.28.22-.5.5-.5H4v-.5c0-.28.22-.5.5-.5H7V1l2-1 2 1 2-1v3l-2 1-2-1-1 .5V5h2.5c.28 0 .5.22.5.5V6h2.5c.28 0 .5.22.5.5V14H9Zm3-6v2h1V8h-1Zm-2 0v2h1V8h-1Zm0 3v2h1v-2h-1Zm2 0v2h1v-2h-1Zm-8 0v2h1v-2H4Zm-2 0v2h1v-2H2Zm6-3v2h1V8H8ZM6 8v2h1V8H6ZM4 8v2h1V8H4ZM2 8v2h1V8H2Z"/></svg>
|
After Width: | Height: | Size: 359 B |
1
umap/static/umap/img/tags/science.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="m8.34 6.15 3.62 8.15a.501.501 0 1 1-.92.4L9.84 12H5.16l-1.2 2.7a.501.501 0 1 1-.92-.4l3.19-7.17-1.74.81-1.27-2.71 6.79-3.17 1.27 2.71-2.94 1.38Zm-.84.58L5.6 11h3.8L7.5 6.73Zm2.76-5.34L12.98.12l1.69 3.63-2.72 1.27-1.69-3.63Zm-10 5.77 2.72-1.27.84 1.81L1.1 8.97.26 7.16Z"/></svg>
|
After Width: | Height: | Size: 346 B |
1
umap/static/umap/img/tags/shopping.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M13.33 5H11.5l-.39-2.33A2 2 0 0 0 9.7 1.18 3.76 3.76 0 0 0 8.62 1H6.38a3.76 3.76 0 0 0-1.08.18 2 2 0 0 0-1.41 1.49L3.5 5H1.67a.5.5 0 0 0-.48.65l1.88 6.3A1.5 1.5 0 0 0 4.5 13h6a1.5 1.5 0 0 0 1.42-1.05l1.88-6.3a.5.5 0 0 0-.47-.65zM4.52 5l.36-2.17a.91.91 0 0 1 .74-.7c.246-.078.502-.121.76-.13h2.24c.261.008.52.051.77.13a.91.91 0 0 1 .74.7L10.48 5h-6z"/></svg>
|
After Width: | Height: | Size: 429 B |
1
umap/static/umap/img/tags/sport.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="M11.968 10.227a3.812 3.812 0 0 1-1.913.984L3.768 4.934a4.028 4.028 0 0 1 1.005-1.902C7.03.774 9.98.224 12.378 2.622s1.848 5.348-.41 7.605Zm-6.987 1.61a3.842 3.842 0 0 1 1.168-.559A4.533 4.533 0 0 1 8 11.445L3.546 7a4.413 4.413 0 0 1 .157 1.922 3.664 3.664 0 0 1-.521 1.116C2.11 11.301 1.05 11.765 1.05 12.226a1.838 1.838 0 0 0 1.724 1.724c.46 0 .918-1.013 2.207-2.113Z"/></svg>
|
After Width: | Height: | Size: 449 B |
1
umap/static/umap/img/tags/tourism.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15"><path d="m5.36 1.67-.01 4.02a4.452 4.452 0 0 0-1.1-.11c-.37.1-.74.63-1.1.76a4.202 4.202 0 0 1 2.21-4.67Zm2.41-.64L9.8 4.48a3.183 3.183 0 0 1 .84-.61c.36-.1.94.17 1.34.11a4.202 4.202 0 0 0-4.21-2.95ZM1 13h13c-.66-.66-2.64-1.11-4.34-1.33l-1.87-7c.52-.05 1.15.03 1.53 0l-2.11-3.6H7.2a6.174 6.174 0 0 0-.7.14 4.38 4.38 0 0 0-.64.22l-.01 4.15c.35-.17.84-.54 1.3-.74l1.8 6.74c-.58-.05-1.09-.08-1.45-.08C6.03 11.5 2 12 1 13Z"/></svg>
|
After Width: | Height: | Size: 489 B |
1
umap/static/umap/img/tags/transports.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15"><path d="M8 1v1h1.5l1.41 1.41c.38.38.59.89.59 1.42V11c0 1.1-.9 2-2 2h-4c-1.1 0-2-.9-2-2V4.83c0-.53.21-1.04.59-1.42L5.5 2H7V1H5.5C5.22 1 5 .78 5 .5s.22-.5.5-.5h4c.28 0 .5.22.5.5s-.22.5-.5.5H8ZM6.25 13.5 5.5 15H4l.75-1.5h1.5Zm4 0L11 15H9.5l-.75-1.5h1.5ZM8.5 12h1c.55 0 1-.45 1-1v-1c-1.1 0-2 .9-2 2Zm-2 0c0-1.1-.9-2-2-2v1c0 .55.45 1 1 1h1Zm-2-6.5v3c0 .28.22.5.5.5h5c.28 0 .5-.22.5-.5v-3c0-.28-.22-.5-.5-.5H5c-.28 0-.5.22-.5.5Zm1-2c0 .28.22.5.5.5h3c.28 0 .5-.22.5-.5S9.28 3 9 3H6c-.28 0-.5.22-.5.5Z"/></svg>
|
After Width: | Height: | Size: 563 B |
1
umap/static/umap/img/tags/travel.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M3.98 36h12.04a.98.98 0 0 0 .98-.98V17a.98.98 0 0 0-1.8-.54L3.17 34.48A.98.98 0 0 0 3.98 36ZM20 36h23.99A1 1 0 0 0 45 34.98C44.58 16.6 32.84.76 20.03 0A.99.99 0 0 0 19 1v34a1 1 0 0 0 1 1ZM46 39H2a2 2 0 0 0-2 2 7 7 0 0 0 7 7h34a7 7 0 0 0 7-7 2 2 0 0 0-2-2Z"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -141,6 +141,7 @@ export class MutatingForm extends Form {
|
|||
facetKey: 'PropertyInput',
|
||||
slugKey: 'PropertyInput',
|
||||
labelKey: 'PropertyInput',
|
||||
tags: 'TagsEditor',
|
||||
}
|
||||
for (const [key, defaults] of Object.entries(SCHEMA)) {
|
||||
const properties = Object.assign({}, defaults)
|
||||
|
@ -152,6 +153,8 @@ export class MutatingForm extends Form {
|
|||
} else if (properties.type === Number) {
|
||||
if (properties.step) properties.handler = 'Range'
|
||||
else properties.handler = 'IntInput'
|
||||
} else if (properties.type === Array) {
|
||||
properties.handler = 'CheckBoxes'
|
||||
} else if (properties.choices) {
|
||||
const text_length = properties.choices.reduce(
|
||||
(acc, [_, label]) => acc + label.length,
|
||||
|
|
|
@ -324,6 +324,41 @@ Fields.CheckBox = class extends BaseElement {
|
|||
}
|
||||
}
|
||||
|
||||
Fields.CheckBoxes = class extends BaseElement {
|
||||
getInputTemplate(value, label) {
|
||||
return `<label><input type=checkbox value="${value}" name="${this.name}" data-ref=input />${label}</label>`
|
||||
}
|
||||
|
||||
build() {
|
||||
const initial = this.get() || []
|
||||
for (const [value, label] of this.properties.choices) {
|
||||
const [root, { input }] = Utils.loadTemplateWithRefs(
|
||||
this.getInputTemplate(value, label)
|
||||
)
|
||||
this.container.appendChild(root)
|
||||
input.checked = initial.includes(value)
|
||||
input.addEventListener('change', () => this.sync())
|
||||
}
|
||||
super.build()
|
||||
}
|
||||
|
||||
value() {
|
||||
return Array.from(this.root.querySelectorAll('input:checked')).map((el) => el.value)
|
||||
}
|
||||
}
|
||||
|
||||
Fields.TagsEditor = class extends Fields.CheckBoxes {
|
||||
getInputTemplate(value, label) {
|
||||
const path = SCHEMA.iconUrl.default.replace('marker.svg', `tags/${value}.svg`)
|
||||
return `
|
||||
<label>
|
||||
<input type=checkbox value="${value}" name="${this.name}" data-ref=input />
|
||||
<img class="tag-icon" src="${path}" alt="" /> ${label}
|
||||
</label>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
Fields.Select = class extends BaseElement {
|
||||
getTemplate() {
|
||||
return `<select name="${this.name}" data-ref=select></select>`
|
||||
|
@ -1296,13 +1331,14 @@ Fields.ManageEditors = class extends BaseElement {
|
|||
placeholder: translate("Type editor's username"),
|
||||
}
|
||||
this.autocomplete = new AjaxAutocompleteMultiple(this.container, options)
|
||||
this._values = this.toHTML()
|
||||
if (this._values)
|
||||
this._values = this.toHTML() || []
|
||||
if (this._values) {
|
||||
for (let i = 0; i < this._values.length; i++)
|
||||
this.autocomplete.displaySelected({
|
||||
item: { value: this._values[i].id, label: this._values[i].name },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
value() {
|
||||
return this._values
|
||||
|
|
|
@ -94,6 +94,9 @@ export default class Importer extends Utils.WithTemplate {
|
|||
case 'datasets':
|
||||
import('./importers/datasets.js').then(register)
|
||||
break
|
||||
case 'banfr':
|
||||
import('./importers/banfr.js').then(register)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +176,7 @@ export default class Importer extends Utils.WithTemplate {
|
|||
showImporters() {
|
||||
if (!this.IMPORTERS.length) return
|
||||
const [element, { grid }] = Utils.loadTemplateWithRefs(GRID_TEMPLATE)
|
||||
for (const plugin of this.IMPORTERS.sort((a, b) => (a.id > b.id ? 1 : -1))) {
|
||||
for (const plugin of this.IMPORTERS.sort((a, b) => (a.name > b.name ? 1 : -1))) {
|
||||
const button = Utils.loadTemplate(
|
||||
`<li><button type="button" class="${plugin.id}">${plugin.name}</button></li>`
|
||||
)
|
||||
|
|
93
umap/static/umap/js/modules/importers/banfr.js
Normal file
|
@ -0,0 +1,93 @@
|
|||
import { DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import { BaseAjax, SingleMixin } from '../autocomplete.js'
|
||||
import * as Utils from '../utils.js'
|
||||
import { AutocompleteCommunes } from './communesfr.js'
|
||||
import { translate } from '../i18n.js'
|
||||
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
||||
|
||||
const TEMPLATE = `
|
||||
<div>
|
||||
<h3>Géocodage d’adresses en France</h3>
|
||||
<p>Géocoder un fichier CSV avec la base adresse nationale.</p>
|
||||
<fieldset class="formbox">
|
||||
<legend>Choisir un fichier CSV (encodé en UTF-8)</legend>
|
||||
<input type=file name=file data-ref=csvFile accept=".csv" />
|
||||
</fieldset>
|
||||
<fieldset class="formbox">
|
||||
<legend>Aperçu des données</legend>
|
||||
<table class="table-scrollable" data-ref=table></table>
|
||||
</fieldset>
|
||||
<fieldset class="formbox">
|
||||
<legend>Sélectionner les colonnes à utiliser</legend>
|
||||
<span data-ref="columns"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
`
|
||||
|
||||
export class Importer {
|
||||
constructor(umap, options) {
|
||||
this._umap = umap
|
||||
this.name = options.name || 'Géocodage FR'
|
||||
this.id = 'banfr'
|
||||
}
|
||||
|
||||
async open(importer) {
|
||||
let data
|
||||
const [container, { table, columns, csvFile }] =
|
||||
Utils.loadTemplateWithRefs(TEMPLATE)
|
||||
csvFile.addEventListener('change', () => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (evt) => {
|
||||
data = evt.target.result
|
||||
const rows = csv2geojson.auto(data).slice(0, 5)
|
||||
const cols = Object.keys(rows[0])
|
||||
table.innerHTML = ''
|
||||
columns.innerHTML = ''
|
||||
const tr = document.createElement('tr')
|
||||
for (const column of cols) {
|
||||
tr.appendChild(Utils.loadTemplate(`<th>${column}</th>`))
|
||||
columns.appendChild(
|
||||
Utils.loadTemplate(
|
||||
`<label><input type="checkbox" value="${column}" /> ${column}</label>`
|
||||
)
|
||||
)
|
||||
}
|
||||
table.appendChild(tr)
|
||||
for (const row of rows) {
|
||||
const tr = document.createElement('tr')
|
||||
for (const column of cols) {
|
||||
tr.appendChild(Utils.loadTemplate(`<td>${row[column]}</td>`))
|
||||
}
|
||||
table.appendChild(tr)
|
||||
}
|
||||
}
|
||||
reader.readAsText(csvFile.files[0])
|
||||
})
|
||||
|
||||
const confirm = async (form) => {
|
||||
const formData = new FormData()
|
||||
formData.append('data', csvFile.files[0])
|
||||
for (const option of columns.querySelectorAll('input:checked')) {
|
||||
formData.append('columns', option.value)
|
||||
}
|
||||
const response = await this._umap.request.post(
|
||||
'https://api-adresse.data.gouv.fr/search/csv/',
|
||||
{},
|
||||
formData
|
||||
)
|
||||
if (response?.ok) {
|
||||
importer.raw = await response.text()
|
||||
importer.format = 'csv'
|
||||
}
|
||||
}
|
||||
|
||||
importer.dialog
|
||||
.open({
|
||||
template: container,
|
||||
className: `${this.id} importer dark`,
|
||||
cancel: false,
|
||||
accept: translate('Geocode'),
|
||||
})
|
||||
.then(confirm)
|
||||
}
|
||||
}
|
|
@ -500,6 +500,9 @@ export const SCHEMA = {
|
|||
helpEntries: ['sync'],
|
||||
default: false,
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
},
|
||||
tilelayer: {
|
||||
type: Object,
|
||||
impacts: ['background'],
|
||||
|
|
|
@ -757,6 +757,12 @@ export default class Umap extends ServerStored {
|
|||
const form = builder.build()
|
||||
container.appendChild(form)
|
||||
|
||||
const tags = DomUtil.createFieldset(container, translate('Tags'))
|
||||
const tagsFields = ['properties.tags']
|
||||
const tagsBuilder = new MutatingForm(this, tagsFields, {
|
||||
umap: this,
|
||||
})
|
||||
tags.appendChild(tagsBuilder.build())
|
||||
const credits = DomUtil.createFieldset(container, translate('Credits'))
|
||||
const creditsFields = [
|
||||
'properties.licence',
|
||||
|
@ -1185,6 +1191,7 @@ export default class Umap extends ServerStored {
|
|||
const formData = new FormData()
|
||||
formData.append('name', this.properties.name)
|
||||
formData.append('center', JSON.stringify(this.geometry()))
|
||||
formData.append('tags', this.properties.tags || [])
|
||||
formData.append('settings', JSON.stringify(geojson))
|
||||
const uri = this.urls.get('map_save', { map_id: this.id })
|
||||
const [data, _, error] = await this.server.post(uri, {}, formData)
|
||||
|
|
|
@ -955,6 +955,20 @@ a.umap-control-caption,
|
|||
display: block;
|
||||
}
|
||||
|
||||
/* **** */
|
||||
/* Tags */
|
||||
/* **** */
|
||||
|
||||
.tag-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-bottom: -4px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.dark .tag-icon {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
/* *************************** */
|
||||
/* Overriding leaflet defaults */
|
||||
/* *************************** */
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"happen": true,
|
||||
"assert": true,
|
||||
"before": true,
|
||||
"after": true,
|
||||
"it": true,
|
||||
"sinon": true,
|
||||
"enableEdit": true,
|
||||
"disableEdit": true,
|
||||
"changeInputValue": true,
|
||||
"resetMap": true,
|
||||
"initMap": true,
|
||||
"clickCancel": true,
|
||||
"map": true,
|
||||
"qs": true,
|
||||
"qsa": true,
|
||||
"qst": true
|
||||
}
|
||||
}
|
|
@ -1,463 +0,0 @@
|
|||
describe('U.DataLayer', () => {
|
||||
let path = '/map/99/datalayer/update/62/',
|
||||
map,
|
||||
datalayer
|
||||
|
||||
before(async () => {
|
||||
fetchMock.mock(/\/datalayer\/62\/\?.*/, JSON.stringify(RESPONSES.datalayer62_GET))
|
||||
fetchMock.sticky('/map/99/update/settings/', { id: 99 })
|
||||
this.options = {
|
||||
umap_id: 99,
|
||||
}
|
||||
MAP = map = initMap({ umap_id: 99 })
|
||||
const datalayer_options = defaultDatalayerData()
|
||||
await map.initDataLayers([datalayer_options])
|
||||
datalayer = map.getDataLayerByUmapId(62)
|
||||
enableEdit()
|
||||
})
|
||||
after(() => {
|
||||
fetchMock.restore()
|
||||
resetMap()
|
||||
})
|
||||
|
||||
describe('#init()', () => {
|
||||
it('should be added in datalayers index', () => {
|
||||
assert.notEqual(map.datalayers_index.indexOf(datalayer), -1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#edit()', () => {
|
||||
var editButton, form, input, forceButton
|
||||
|
||||
it('row in control should be active', () => {
|
||||
assert.notOk(
|
||||
qs('.leaflet-control-browse #browse_data_toggle_' + L.stamp(datalayer) + '.off')
|
||||
)
|
||||
})
|
||||
|
||||
it('should have edit button', () => {
|
||||
editButton = qs('#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-edit')
|
||||
assert.ok(editButton)
|
||||
})
|
||||
|
||||
it('should have toggle visibility element', () => {
|
||||
assert.ok(qs('.leaflet-control-browse i.layer-toggle'))
|
||||
})
|
||||
|
||||
it('should exist only one datalayer', () => {
|
||||
assert.equal(qsa('.leaflet-control-browse i.layer-toggle').length, 1)
|
||||
})
|
||||
|
||||
it('should build a form on edit button click', () => {
|
||||
happen.click(editButton)
|
||||
form = qs('form.umap-form')
|
||||
input = qs('form.umap-form input[name="name"]')
|
||||
assert.ok(form)
|
||||
assert.ok(input)
|
||||
})
|
||||
|
||||
it('should update name on input change', () => {
|
||||
var new_name = 'This is a new name'
|
||||
input.value = new_name
|
||||
happen.once(input, { type: 'input' })
|
||||
assert.equal(datalayer.options.name, new_name)
|
||||
})
|
||||
|
||||
it('should have made datalayer dirty', () => {
|
||||
assert.ok(datalayer.isDirty)
|
||||
assert.notEqual(map.dirty_datalayers.indexOf(datalayer), -1)
|
||||
})
|
||||
|
||||
it('should have made Map dirty', () => {
|
||||
assert.ok(map.isDirty)
|
||||
})
|
||||
|
||||
it('should call datalayer.save on save button click', (done) => {
|
||||
const postDatalayer = fetchMock.post(path, () => {
|
||||
return defaultDatalayerData()
|
||||
})
|
||||
clickSave()
|
||||
window.setTimeout(() => {
|
||||
assert(fetchMock.called(path))
|
||||
done()
|
||||
}, 500)
|
||||
})
|
||||
|
||||
it('should show alert if server respond 412', (done) => {
|
||||
cleanAlert()
|
||||
fetchMock.restore()
|
||||
fetchMock.post(path, 412)
|
||||
happen.click(editButton)
|
||||
input = qs('form.umap-form input[name="name"]')
|
||||
input.value = 'a new name'
|
||||
happen.once(input, { type: 'input' })
|
||||
clickSave()
|
||||
window.setTimeout(() => {
|
||||
assert(L.DomUtil.hasClass(map._container, 'umap-alert'))
|
||||
assert.notEqual(map.dirty_datalayers.indexOf(datalayer), -1)
|
||||
const forceButton = qs('#umap-alert-container .umap-action')
|
||||
assert.ok(forceButton)
|
||||
done()
|
||||
}, 500)
|
||||
})
|
||||
|
||||
it('should save anyway on force save button click', (done) => {
|
||||
const forceButton = qs('#umap-alert-container .umap-action')
|
||||
fetchMock.restore()
|
||||
fetchMock.post(path, defaultDatalayerData)
|
||||
happen.click(forceButton)
|
||||
window.setTimeout(() => {
|
||||
assert.notOk(qs('#umap-alert-container .umap-action'))
|
||||
assert(fetchMock.called(path))
|
||||
assert.equal(map.dirty_datalayers.indexOf(datalayer), -1)
|
||||
done()
|
||||
}, 500)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#save() new', () => {
|
||||
let newLayerButton, form, input, newDatalayer, editButton, manageButton
|
||||
|
||||
it('should have a manage datalayers action', () => {
|
||||
enableEdit()
|
||||
manageButton = qs('.manage-datalayers')
|
||||
assert.ok(manageButton)
|
||||
happen.click(manageButton)
|
||||
})
|
||||
|
||||
it('should have a new layer button', () => {
|
||||
newLayerButton = qs('.panel.right.on .add-datalayer')
|
||||
assert.ok(newLayerButton)
|
||||
})
|
||||
|
||||
it('should build a form on new layer button click', () => {
|
||||
happen.click(newLayerButton)
|
||||
form = qs('form.umap-form')
|
||||
input = qs('form.umap-form input[name="name"]')
|
||||
assert.ok(form)
|
||||
assert.ok(input)
|
||||
})
|
||||
|
||||
it('should have an empty name', () => {
|
||||
assert.notOk(input.value)
|
||||
})
|
||||
|
||||
it('should have created a new datalayer', () => {
|
||||
assert.equal(map.datalayers_index.length, 2)
|
||||
newDatalayer = map.datalayers_index[1]
|
||||
})
|
||||
|
||||
it('should have made Map dirty', () => {
|
||||
assert.ok(map.isDirty)
|
||||
})
|
||||
|
||||
it('should update name on input change', () => {
|
||||
var new_name = 'This is a new name'
|
||||
input.value = new_name
|
||||
happen.once(input, { type: 'input' })
|
||||
assert.equal(newDatalayer.options.name, new_name)
|
||||
})
|
||||
|
||||
it('should set umap_id on save callback', async () => {
|
||||
assert.notOk(newDatalayer.umap_id)
|
||||
fetchMock.post('/map/99/datalayer/create/', defaultDatalayerData({ id: 63 }))
|
||||
clickSave()
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(() => {
|
||||
assert.equal(newDatalayer.umap_id, 63)
|
||||
resolve()
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have unset map dirty', () => {
|
||||
assert.notOk(map.isDirty)
|
||||
})
|
||||
|
||||
it('should have edit button', () => {
|
||||
editButton = qs('#browse_data_toggle_' + L.stamp(newDatalayer) + ' .layer-edit')
|
||||
assert.ok(editButton)
|
||||
})
|
||||
|
||||
it('should call update if we edit again', async () => {
|
||||
happen.click(editButton)
|
||||
assert.notOk(map.isDirty)
|
||||
input = qs('form.umap-form input[name="name"]')
|
||||
input.value = "a new name again but we don't care which"
|
||||
happen.once(input, { type: 'input' })
|
||||
assert.ok(map.isDirty)
|
||||
var response = () => {
|
||||
return defaultDatalayerData({ pk: 63 })
|
||||
}
|
||||
var spy = sinon.spy(response)
|
||||
fetchMock.post('/map/99/datalayer/update/63/', spy)
|
||||
return new Promise((resolve) => {
|
||||
clickSave()
|
||||
window.setTimeout(() => {
|
||||
assert.ok(spy.calledOnce)
|
||||
resolve()
|
||||
}, 1000)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#iconClassChange()', () => {
|
||||
it('should change icon class', () => {
|
||||
happen.click(qs('[data-id="' + datalayer._leaflet_id + '"] .layer-edit'))
|
||||
changeSelectValue(
|
||||
qs('form#datalayer-advanced-properties select[name=iconClass]'),
|
||||
'Circle'
|
||||
)
|
||||
assert.notOk(qs('div.umap-div-icon'))
|
||||
assert.ok(qs('div.umap-circle-icon'))
|
||||
happen.click(
|
||||
qs('form#datalayer-advanced-properties .umap-field-iconClass .undefine')
|
||||
)
|
||||
assert.notOk(qs('div.umap-circle-icon'))
|
||||
assert.ok(qs('div.umap-div-icon'))
|
||||
clickCancel()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#show/hide', () => {
|
||||
it('should hide features on hide', () => {
|
||||
assert.ok(qs('div.umap-div-icon'))
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
datalayer.hide()
|
||||
assert.notOk(qs('div.umap-div-icon'))
|
||||
assert.notOk(qs('path[fill="none"]'))
|
||||
})
|
||||
|
||||
it('should show features on show', () => {
|
||||
assert.notOk(qs('div.umap-div-icon'))
|
||||
assert.notOk(qs('path[fill="none"]'))
|
||||
datalayer.show()
|
||||
assert.ok(qs('div.umap-div-icon'))
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#clone()', () => {
|
||||
it('should clone everything but the id and the name', () => {
|
||||
enableEdit()
|
||||
var clone = datalayer.clone()
|
||||
assert.notOk(clone.umap_id)
|
||||
assert.notEqual(clone.options.name, datalayer.name)
|
||||
assert.ok(clone.options.name)
|
||||
assert.equal(clone.options.color, datalayer.options.color)
|
||||
assert.equal(clone.options.stroke, datalayer.options.stroke)
|
||||
clone._delete()
|
||||
clickSave()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#restore()', () => {
|
||||
var oldConfirm,
|
||||
newConfirm = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
before(() => {
|
||||
oldConfirm = window.confirm
|
||||
window.confirm = newConfirm
|
||||
})
|
||||
after(() => {
|
||||
window.confirm = oldConfirm
|
||||
})
|
||||
|
||||
it('should restore everything', (done) => {
|
||||
enableEdit()
|
||||
var geojson = L.Util.CopyJSON(RESPONSES.datalayer62_GET)
|
||||
geojson.features.push({
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [-1.274658203125, 50.57634993749885],
|
||||
},
|
||||
type: 'Feature',
|
||||
id: 1807,
|
||||
properties: { _umap_options: {}, name: 'new point from restore' },
|
||||
})
|
||||
geojson._umap_options.color = 'Chocolate'
|
||||
fetchMock.get('/datalayer/62/olderversion.geojson', geojson)
|
||||
sinon.spy(window, 'confirm')
|
||||
datalayer.restore('olderversion.geojson')
|
||||
window.setTimeout(() => {
|
||||
assert(window.confirm.calledOnce)
|
||||
window.confirm.restore()
|
||||
assert.equal(datalayer.umap_id, 62)
|
||||
assert.ok(datalayer.isDirty)
|
||||
assert.equal(datalayer._index.length, 4)
|
||||
assert.ok(qs('path[fill="Chocolate"]'))
|
||||
done()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
it('should revert anything on cancel click', () => {
|
||||
clickCancel()
|
||||
assert.equal(datalayer._index.length, 3)
|
||||
assert.notOk(qs('path[fill="Chocolate"]'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#smart-options()', () => {
|
||||
let poly, marker
|
||||
before(() => {
|
||||
datalayer.eachLayer(function (layer) {
|
||||
if (!poly && layer instanceof L.Polygon) {
|
||||
poly = layer
|
||||
}
|
||||
if (!marker && layer instanceof L.Marker) {
|
||||
marker = layer
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should parse color variable', () => {
|
||||
let icon = qs('div.umap-div-icon .icon_container')
|
||||
poly.properties.mycolor = 'DarkGoldenRod'
|
||||
marker.properties.mycolor = 'DarkRed'
|
||||
marker.properties._umap_options.color = undefined
|
||||
assert.notOk(qs('path[fill="DarkGoldenRod"]'))
|
||||
assert.equal(icon.style.backgroundColor, 'olivedrab')
|
||||
datalayer.options.color = '{mycolor}'
|
||||
datalayer.options.fillColor = '{mycolor}'
|
||||
datalayer.indexProperties(poly)
|
||||
datalayer.indexProperties(marker)
|
||||
datalayer.redraw()
|
||||
icon = qs('div.umap-div-icon .icon_container')
|
||||
assert.equal(icon.style.backgroundColor, 'darkred')
|
||||
assert.ok(qs('path[fill="DarkGoldenRod"]'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#facet-search()', () => {
|
||||
before(async () => {
|
||||
fetchMock.get(/\/datalayer\/63\/\?.*/, RESPONSES.datalayer63_GET)
|
||||
map.options.facetKey = 'name'
|
||||
await map.initDataLayers([RESPONSES.datalayer63_GET._umap_options])
|
||||
})
|
||||
it('should not impact non browsable layer', () => {
|
||||
assert.ok(qs('path[fill="SteelBlue"]'))
|
||||
})
|
||||
it('should allow advanced filter', () => {
|
||||
map.openFacet()
|
||||
assert.ok(qs('div.umap-facet-search'))
|
||||
// This one if from the normal datalayer
|
||||
// it's name is "test", so it should be hidden
|
||||
// by the filter
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
happen.click(qs('input[data-value="name poly"]'))
|
||||
assert.notOk(qs('path[fill="none"]'))
|
||||
// This one comes from a non browsable layer
|
||||
// so it should not be affected by the filter
|
||||
assert.ok(qs('path[fill="SteelBlue"]'))
|
||||
happen.click(qs('input[data-value="name poly"]')) // Undo
|
||||
})
|
||||
it('should allow to control facet label', () => {
|
||||
map.options.facetKey = 'name|Nom'
|
||||
map.openFacet()
|
||||
assert.ok(qs('div.umap-facet-search h5'))
|
||||
assert.equal(qs('div.umap-facet-search h5').textContent, 'Nom')
|
||||
})
|
||||
})
|
||||
describe('#zoomEnd', () => {
|
||||
it('should honour the fromZoom option', () => {
|
||||
map.setZoom(6, { animate: false })
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
datalayer.options.fromZoom = 6
|
||||
map.setZoom(5, { animate: false })
|
||||
assert.notOk(qs('path[fill="none"]'))
|
||||
map.setZoom(6, { animate: false })
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
})
|
||||
|
||||
it('should honour the toZoom option', () => {
|
||||
map.setZoom(6, { animate: false })
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
datalayer.options.toZoom = 6
|
||||
map.setZoom(7, { animate: false })
|
||||
assert.notOk(qs('path[fill="none"]'))
|
||||
map.setZoom(6, { animate: false })
|
||||
assert.ok(qs('path[fill="none"]'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('#displayOnLoad', () => {
|
||||
before(() => {
|
||||
fetchMock.get(/\/datalayer\/64\/\?.*/, RESPONSES.datalayer64_GET)
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await map.initDataLayers([RESPONSES.datalayer64_GET._umap_options])
|
||||
datalayer = map.getDataLayerByUmapId(64)
|
||||
map.setZoom(10, { animate: false })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
datalayer._delete()
|
||||
})
|
||||
|
||||
it('should not display layer at load', () => {
|
||||
assert.notOk(qs('path[fill="AliceBlue"]'))
|
||||
})
|
||||
|
||||
it('should display on click', (done) => {
|
||||
happen.click(qs(`[data-id='${L.stamp(datalayer)}'] .layer-toggle`))
|
||||
window.setTimeout(() => {
|
||||
assert.ok(qs('path[fill="AliceBlue"]'))
|
||||
done()
|
||||
}, 500)
|
||||
})
|
||||
|
||||
it('should not display on zoom', (done) => {
|
||||
map.setZoom(9, { animate: false })
|
||||
window.setTimeout(() => {
|
||||
assert.notOk(qs('path[fill="AliceBlue"]'))
|
||||
done()
|
||||
}, 500)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#delete()', () => {
|
||||
let deleteLink,
|
||||
deletePath = '/map/99/datalayer/delete/62/'
|
||||
before(() => {
|
||||
datalayer = map.getDataLayerByUmapId(62)
|
||||
})
|
||||
|
||||
it('should have a delete link in update form', () => {
|
||||
enableEdit()
|
||||
happen.click(qs('#browse_data_toggle_' + L.stamp(datalayer) + ' .layer-edit'))
|
||||
deleteLink = qs('button.delete_datalayer_button')
|
||||
assert.ok(deleteLink)
|
||||
})
|
||||
|
||||
it('should delete features on datalayer delete', () => {
|
||||
happen.click(deleteLink)
|
||||
assert.notOk(qs('div.icon_container'))
|
||||
})
|
||||
|
||||
it('should have set map dirty', () => {
|
||||
assert.ok(map.isDirty)
|
||||
})
|
||||
|
||||
it('should delete layer control row on delete', () => {
|
||||
assert.notOk(
|
||||
qs('.leaflet-control-browse #browse_data_toggle_' + L.stamp(datalayer))
|
||||
)
|
||||
})
|
||||
|
||||
it('should be removed from map.datalayers_index', () => {
|
||||
assert.equal(map.datalayers_index.indexOf(datalayer), -1)
|
||||
})
|
||||
|
||||
it('should be removed from map.datalayers', () => {
|
||||
assert.notOk(map.datalayers[L.stamp(datalayer)])
|
||||
})
|
||||
|
||||
it('should be visible again on edit cancel', () => {
|
||||
clickCancel()
|
||||
assert.ok(qs('div.icon_container'))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,131 +0,0 @@
|
|||
describe('U.FeatureMixin', function () {
|
||||
let map, datalayer
|
||||
before(async () => {
|
||||
await fetchMock.mock(
|
||||
/\/datalayer\/62\/\?.*/,
|
||||
JSON.stringify(RESPONSES.datalayer62_GET)
|
||||
)
|
||||
this.options = {
|
||||
umap_id: 99,
|
||||
}
|
||||
MAP = map = initMap({ umap_id: 99 })
|
||||
const datalayer_options = defaultDatalayerData()
|
||||
await map.initDataLayers([datalayer_options])
|
||||
datalayer = map.getDataLayerByUmapId(62)
|
||||
})
|
||||
after(function () {
|
||||
fetchMock.restore()
|
||||
resetMap()
|
||||
})
|
||||
|
||||
describe('#utils()', function () {
|
||||
var poly, marker
|
||||
function setFeatures(datalayer) {
|
||||
datalayer.eachLayer(function (layer) {
|
||||
if (!poly && layer instanceof L.Polygon) {
|
||||
poly = layer
|
||||
}
|
||||
if (!marker && layer instanceof L.Marker) {
|
||||
marker = layer
|
||||
}
|
||||
})
|
||||
}
|
||||
it('should generate a valid geojson', function () {
|
||||
setFeatures(datalayer)
|
||||
assert.ok(poly)
|
||||
assert.deepEqual(poly.toGeoJSON().geometry, {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[11.25, 53.585984],
|
||||
[10.151367, 52.975108],
|
||||
[12.689209, 52.167194],
|
||||
[14.084473, 53.199452],
|
||||
[12.634277, 53.618579],
|
||||
[11.25, 53.585984],
|
||||
[11.25, 53.585984],
|
||||
],
|
||||
],
|
||||
})
|
||||
// Ensure original latlngs has not been modified
|
||||
assert.equal(poly.getLatLngs()[0].length, 6)
|
||||
})
|
||||
|
||||
it('should remove empty _umap_options from exported geojson', function () {
|
||||
setFeatures(datalayer)
|
||||
assert.ok(poly)
|
||||
assert.deepEqual(poly.toGeoJSON().properties, { name: 'name poly' })
|
||||
assert.ok(marker)
|
||||
assert.deepEqual(marker.toGeoJSON().properties, {
|
||||
_umap_options: { color: 'OliveDrab' },
|
||||
name: 'test',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#properties()', function () {
|
||||
it('should rename property', function () {
|
||||
var poly = datalayer._lineToLayer({}, [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
])
|
||||
poly.properties.prop1 = 'xxx'
|
||||
poly.renameProperty('prop1', 'prop2')
|
||||
assert.equal(poly.properties.prop2, 'xxx')
|
||||
assert.ok(typeof poly.properties.prop1 === 'undefined')
|
||||
})
|
||||
|
||||
it('should not create property when renaming', function () {
|
||||
var poly = datalayer._lineToLayer({}, [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
])
|
||||
delete poly.properties.prop2 // Make sure it doesn't exist
|
||||
poly.renameProperty('prop1', 'prop2')
|
||||
assert.ok(typeof poly.properties.prop2 === 'undefined')
|
||||
})
|
||||
|
||||
it('should delete property', function () {
|
||||
var poly = datalayer._lineToLayer({}, [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
])
|
||||
poly.properties.prop = 'xxx'
|
||||
assert.equal(poly.properties.prop, 'xxx')
|
||||
poly.deleteProperty('prop')
|
||||
assert.ok(typeof poly.properties.prop === 'undefined')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#matchFilter()', function () {
|
||||
var poly
|
||||
|
||||
it('should filter on properties', function () {
|
||||
poly = datalayer._lineToLayer({}, [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
])
|
||||
poly.properties.name = 'mooring'
|
||||
assert.ok(poly.matchFilter('moo', ['name']))
|
||||
assert.notOk(poly.matchFilter('foo', ['name']))
|
||||
})
|
||||
|
||||
it('should be case unsensitive', function () {
|
||||
assert.ok(poly.matchFilter('Moo', ['name']))
|
||||
})
|
||||
|
||||
it('should match also in the middle of a string', function () {
|
||||
assert.ok(poly.matchFilter('oor', ['name']))
|
||||
})
|
||||
|
||||
it('should handle multiproperties', function () {
|
||||
poly.properties.city = 'Teulada'
|
||||
assert.ok(poly.matchFilter('eul', ['name', 'city', 'foo']))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,37 +0,0 @@
|
|||
describe('U.Map', () => {
|
||||
let map, datalayer
|
||||
before(async () => {
|
||||
await fetchMock.mock(
|
||||
/\/datalayer\/62\/\?.*/,
|
||||
JSON.stringify(RESPONSES.datalayer62_GET)
|
||||
)
|
||||
this.options = {
|
||||
umap_id: 99,
|
||||
}
|
||||
map = initMap({ umap_id: 99 })
|
||||
const datalayer_options = defaultDatalayerData()
|
||||
await map.initDataLayers([datalayer_options])
|
||||
datalayer = map.getDataLayerByUmapId(62)
|
||||
})
|
||||
after(() => {
|
||||
fetchMock.restore()
|
||||
clickCancel()
|
||||
resetMap()
|
||||
})
|
||||
|
||||
describe('#localizeUrl()', function () {
|
||||
it('should replace known variables', function () {
|
||||
assert.equal(
|
||||
map.localizeUrl('http://example.org/{zoom}'),
|
||||
'http://example.org/' + map.getZoom()
|
||||
)
|
||||
})
|
||||
|
||||
it('should keep unknown variables', function () {
|
||||
assert.equal(
|
||||
map.localizeUrl('http://example.org/{unkown}'),
|
||||
'http://example.org/{unkown}'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,126 +0,0 @@
|
|||
describe('U.Marker', () => {
|
||||
let map, datalayer
|
||||
before(async () => {
|
||||
const datalayer_response = JSON.parse(JSON.stringify(RESPONSES.datalayer62_GET)) // Copy.
|
||||
datalayer_response._umap_options.iconClass = 'Drop'
|
||||
await fetchMock.mock(/\/datalayer\/62\/\?.*/, datalayer_response)
|
||||
this.options = {
|
||||
umap_id: 99,
|
||||
}
|
||||
MAP = map = initMap({ umap_id: 99 })
|
||||
const datalayer_options = defaultDatalayerData()
|
||||
await map.initDataLayers([datalayer_options])
|
||||
datalayer = map.getDataLayerByUmapId(62)
|
||||
})
|
||||
after(() => {
|
||||
fetchMock.restore()
|
||||
resetMap()
|
||||
})
|
||||
|
||||
describe('#iconClassChange()', () => {
|
||||
it('should change icon class', () => {
|
||||
enableEdit()
|
||||
happen.click(qs('div.umap-drop-icon'))
|
||||
happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit'))
|
||||
changeSelectValue(
|
||||
qs(
|
||||
'form#umap-feature-shape-properties .umap-field-iconClass select[name=iconClass]'
|
||||
),
|
||||
'Circle'
|
||||
)
|
||||
assert.notOk(qs('div.umap-drop-icon'))
|
||||
assert.ok(qs('div.umap-circle-icon'))
|
||||
happen.click(
|
||||
qs('form#umap-feature-shape-properties .umap-field-iconClass .undefine')
|
||||
)
|
||||
assert.notOk(qs('div.umap-circle-icon'))
|
||||
assert.ok(qs('div.umap-drop-icon'))
|
||||
clickCancel()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#iconSymbolChange()', () => {
|
||||
it('should change icon symbol', () => {
|
||||
enableEdit()
|
||||
happen.click(qs('div.umap-drop-icon'))
|
||||
happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit'))
|
||||
changeInputValue(
|
||||
qs(
|
||||
'form#umap-feature-shape-properties .umap-field-iconUrl input[name=iconUrl]'
|
||||
),
|
||||
'1'
|
||||
)
|
||||
assert.equal(qs('div.umap-drop-icon span').textContent, '1')
|
||||
changeInputValue(
|
||||
qs(
|
||||
'form#umap-feature-shape-properties .umap-field-iconUrl input[name=iconUrl]'
|
||||
),
|
||||
'{name}'
|
||||
)
|
||||
assert.equal(qs('div.umap-drop-icon span').textContent, 'test')
|
||||
clickCancel()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#iconClassChange()', () => {
|
||||
it('should change icon class', () => {
|
||||
enableEdit()
|
||||
happen.click(qs('div.umap-drop-icon'))
|
||||
happen.click(qs('ul.leaflet-inplace-toolbar a.umap-toggle-edit'))
|
||||
changeSelectValue(
|
||||
qs(
|
||||
'form#umap-feature-shape-properties .umap-field-iconClass select[name=iconClass]'
|
||||
),
|
||||
'Circle'
|
||||
)
|
||||
assert.notOk(qs('div.umap-drop-icon'))
|
||||
assert.ok(qs('div.umap-circle-icon'))
|
||||
happen.click(
|
||||
qs('form#umap-feature-shape-properties .umap-field-iconClass .undefine')
|
||||
)
|
||||
assert.notOk(qs('div.umap-circle-icon'))
|
||||
assert.ok(qs('div.umap-drop-icon'))
|
||||
clickCancel()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#clone', () => {
|
||||
it('should clone marker', () => {
|
||||
var layer = new U.Marker(map, [10, 20], {
|
||||
datalayer: datalayer,
|
||||
}).addTo(datalayer)
|
||||
assert.equal(datalayer._index.length, 4)
|
||||
other = layer.clone()
|
||||
assert.ok(map.hasLayer(other))
|
||||
assert.equal(datalayer._index.length, 5)
|
||||
// Must not be the same reference
|
||||
assert.notEqual(layer._latlng, other._latlng)
|
||||
assert.equal(L.Util.formatNum(layer._latlng.lat), other._latlng.lat)
|
||||
assert.equal(L.Util.formatNum(layer._latlng.lng), other._latlng.lng)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#edit()', function (done) {
|
||||
it('should allow changing coordinates manually', () => {
|
||||
var layer = new U.Marker(map, [10, 20], {
|
||||
datalayer: datalayer,
|
||||
}).addTo(datalayer)
|
||||
enableEdit()
|
||||
layer.edit()
|
||||
changeInputValue(qs('form.umap-form input[name="lat"]'), '54.43')
|
||||
assert.equal(layer._latlng.lat, 54.43)
|
||||
})
|
||||
|
||||
it('should not allow invalid latitude nor longitude', () => {
|
||||
var layer = new U.Marker(map, [10, 20], {
|
||||
datalayer: datalayer,
|
||||
}).addTo(datalayer)
|
||||
enableEdit()
|
||||
layer.edit()
|
||||
changeInputValue(qs('form.umap-form input[name="lat"]'), '5443')
|
||||
assert.equal(layer._latlng.lat, 10)
|
||||
changeInputValue(qs('form.umap-form input[name="lng"]'), '5443')
|
||||
assert.equal(layer._latlng.lng, 20)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,111 +0,0 @@
|
|||
describe('U.Polygon', function () {
|
||||
var p2ll, map, datalayer
|
||||
|
||||
before(function () {
|
||||
map = initMap({ umap_id: 99 })
|
||||
enableEdit()
|
||||
p2ll = function (x, y) {
|
||||
return map.containerPointToLatLng([x, y])
|
||||
}
|
||||
datalayer = map.createDataLayer()
|
||||
datalayer.connectToMap()
|
||||
})
|
||||
|
||||
after(function () {
|
||||
clickCancel()
|
||||
resetMap()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
datalayer.empty()
|
||||
})
|
||||
|
||||
describe('#isMulti()', function () {
|
||||
it('should return false for basic Polygon', function () {
|
||||
var layer = new U.Polygon(
|
||||
map,
|
||||
[
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
],
|
||||
{ datalayer: datalayer }
|
||||
)
|
||||
assert.notOk(layer.isMulti())
|
||||
})
|
||||
|
||||
it('should return false for nested basic Polygon', function () {
|
||||
var latlngs = [[[p2ll(100, 150), p2ll(150, 200), p2ll(200, 100)]]],
|
||||
layer = new U.Polygon(map, latlngs, { datalayer: datalayer })
|
||||
assert.notOk(layer.isMulti())
|
||||
})
|
||||
|
||||
it('should return false for simple Polygon with hole', function () {
|
||||
var layer = new U.Polygon(
|
||||
map,
|
||||
[
|
||||
[
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
],
|
||||
[
|
||||
[7, 8],
|
||||
[9, 10],
|
||||
[11, 12],
|
||||
],
|
||||
],
|
||||
{ datalayer: datalayer }
|
||||
)
|
||||
assert.notOk(layer.isMulti())
|
||||
})
|
||||
|
||||
it('should return true for multi Polygon', function () {
|
||||
var latLngs = [
|
||||
[
|
||||
[
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[7, 8],
|
||||
[9, 10],
|
||||
[11, 12],
|
||||
],
|
||||
],
|
||||
]
|
||||
var layer = new U.Polygon(map, latLngs, { datalayer: datalayer })
|
||||
assert.ok(layer.isMulti())
|
||||
})
|
||||
|
||||
it('should return true for multi Polygon with hole', function () {
|
||||
var latLngs = [
|
||||
[
|
||||
[
|
||||
[10, 20],
|
||||
[30, 40],
|
||||
[50, 60],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[0, 10],
|
||||
[10, 10],
|
||||
[10, 0],
|
||||
],
|
||||
[
|
||||
[2, 3],
|
||||
[2, 4],
|
||||
[3, 4],
|
||||
],
|
||||
],
|
||||
]
|
||||
var layer = new U.Polygon(map, latLngs, { datalayer: datalayer })
|
||||
assert.ok(layer.isMulti())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,286 +0,0 @@
|
|||
describe('U.Polyline', function () {
|
||||
var p2ll, map
|
||||
|
||||
before(function () {
|
||||
this.map = map = initMap({ umap_id: 99 })
|
||||
enableEdit()
|
||||
p2ll = function (x, y) {
|
||||
return map.containerPointToLatLng([x, y])
|
||||
}
|
||||
this.datalayer = this.map.createDataLayer()
|
||||
this.datalayer.connectToMap()
|
||||
})
|
||||
|
||||
after(function () {
|
||||
clickCancel()
|
||||
resetMap()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
this.datalayer.empty()
|
||||
})
|
||||
|
||||
describe('#isMulti()', function () {
|
||||
it('should return false for basic Polyline', function () {
|
||||
var layer = new U.Polyline(
|
||||
this.map,
|
||||
[
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
],
|
||||
{ datalayer: this.datalayer }
|
||||
)
|
||||
assert.notOk(layer.isMulti())
|
||||
})
|
||||
|
||||
it('should return false for nested basic Polyline', function () {
|
||||
var layer = new U.Polyline(
|
||||
this.map,
|
||||
[
|
||||
[
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
],
|
||||
],
|
||||
{ datalayer: this.datalayer }
|
||||
)
|
||||
assert.notOk(layer.isMulti())
|
||||
})
|
||||
|
||||
it('should return true for multi Polyline', function () {
|
||||
var latLngs = [
|
||||
[
|
||||
[
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
[7, 8],
|
||||
[9, 10],
|
||||
[11, 12],
|
||||
],
|
||||
],
|
||||
]
|
||||
var layer = new U.Polyline(this.map, latLngs, { datalayer: this.datalayer })
|
||||
assert.ok(layer.isMulti())
|
||||
})
|
||||
})
|
||||
|
||||
describe('#contextmenu', function () {
|
||||
afterEach(function () {
|
||||
// Make sure contextmenu is hidden.
|
||||
happen.once(document, { type: 'keydown', keyCode: 27 })
|
||||
})
|
||||
|
||||
describe('#in edit mode', function () {
|
||||
it('should allow to remove shape when multi', function () {
|
||||
var latlngs = [
|
||||
[p2ll(100, 100), p2ll(100, 200)],
|
||||
[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)],
|
||||
],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.equal(qst('Remove shape from the multi'), 1)
|
||||
})
|
||||
|
||||
it('should not allow to remove shape when not multi', function () {
|
||||
var latlngs = [[p2ll(100, 100), p2ll(100, 200)]],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.notOk(qst('Remove shape from the multi'))
|
||||
})
|
||||
|
||||
it('should not allow to isolate shape when not multi', function () {
|
||||
var latlngs = [[p2ll(100, 100), p2ll(100, 200)]],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.notOk(qst('Extract shape to separate feature'))
|
||||
})
|
||||
|
||||
it('should allow to isolate shape when multi', function () {
|
||||
var latlngs = [
|
||||
[p2ll(100, 150), p2ll(100, 200)],
|
||||
[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)],
|
||||
],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.ok(qst('Extract shape to separate feature'))
|
||||
})
|
||||
|
||||
it('should not allow to transform to polygon when multi', function () {
|
||||
var latlngs = [
|
||||
[p2ll(100, 150), p2ll(100, 200)],
|
||||
[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)],
|
||||
],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.notOk(qst('Transform to polygon'))
|
||||
})
|
||||
|
||||
it('should allow to transform to polygon when not multi', function () {
|
||||
var latlngs = [p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.equal(qst('Transform to polygon'), 1)
|
||||
})
|
||||
|
||||
it('should not allow to transfer shape when not editedFeature', function () {
|
||||
var layer = new U.Polyline(this.map, [p2ll(100, 150), p2ll(100, 200)], {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.notOk(qst('Transfer shape to edited feature'))
|
||||
})
|
||||
|
||||
it('should not allow to transfer shape when editedFeature is not a line', function () {
|
||||
var layer = new U.Polyline(this.map, [p2ll(100, 150), p2ll(100, 200)], {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer),
|
||||
other = new U.Polygon(
|
||||
this.map,
|
||||
[p2ll(200, 300), p2ll(300, 200), p2ll(200, 100)],
|
||||
{ datalayer: this.datalayer }
|
||||
).addTo(this.datalayer)
|
||||
other.edit()
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.notOk(qst('Transfer shape to edited feature'))
|
||||
})
|
||||
|
||||
it('should allow to transfer shape when another line is edited', function () {
|
||||
var layer = new U.Polyline(
|
||||
this.map,
|
||||
[p2ll(100, 150), p2ll(100, 200), p2ll(200, 100)],
|
||||
{ datalayer: this.datalayer }
|
||||
).addTo(this.datalayer),
|
||||
other = new U.Polyline(this.map, [p2ll(200, 300), p2ll(300, 200)], {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
other.edit()
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.equal(qst('Transfer shape to edited feature'), 1)
|
||||
other.remove()
|
||||
layer.remove()
|
||||
})
|
||||
|
||||
it('should allow to merge lines when multi', function () {
|
||||
var latlngs = [
|
||||
[p2ll(100, 100), p2ll(100, 200)],
|
||||
[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)],
|
||||
],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.equal(qst('Merge lines'), 1)
|
||||
})
|
||||
|
||||
it('should not allow to merge lines when not multi', function () {
|
||||
var latlngs = [[p2ll(100, 100), p2ll(100, 200)]],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
happen.once(layer._path, { type: 'contextmenu' })
|
||||
assert.notOk(qst('Merge lines'))
|
||||
})
|
||||
|
||||
it('should allow to split lines when clicking on vertex', function () {
|
||||
var latlngs = [[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)]],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
layer.enableEdit()
|
||||
happen.at('contextmenu', 350, 400)
|
||||
assert.equal(qst('Split line'), 1)
|
||||
})
|
||||
|
||||
it('should not allow to split lines when clicking on first vertex', function () {
|
||||
var latlngs = [[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)]],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
layer.enableEdit()
|
||||
happen.at('contextmenu', 300, 350)
|
||||
assert.equal(qst('Delete this feature'), 1) // Make sure we have clicked on the vertex.
|
||||
assert.notOk(qst('Split line'))
|
||||
})
|
||||
|
||||
it('should not allow to split lines when clicking on last vertex', function () {
|
||||
var latlngs = [[p2ll(300, 350), p2ll(350, 400), p2ll(400, 300)]],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
layer.enableEdit()
|
||||
happen.at('contextmenu', 400, 300)
|
||||
assert.equal(qst('Delete this feature'), 1) // Make sure we have clicked on the vertex.
|
||||
assert.notOk(qst('Split line'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#mergeShapes', function () {
|
||||
it('should remove duplicated join point when merging', function () {
|
||||
var latlngs = [
|
||||
[
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
],
|
||||
[
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
],
|
||||
],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
layer.mergeShapes()
|
||||
layer.disableEdit() // Remove vertex from latlngs to compare them.
|
||||
assert.deepEqual(layer.getLatLngs(), [
|
||||
L.latLng([0, 0]),
|
||||
L.latLng([0, 1]),
|
||||
L.latLng([0, 2]),
|
||||
])
|
||||
assert(this.map.isDirty)
|
||||
})
|
||||
|
||||
it('should revert candidate if first point is closer', function () {
|
||||
var latlngs = [
|
||||
[
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
],
|
||||
[
|
||||
[0, 2],
|
||||
[0, 1],
|
||||
],
|
||||
],
|
||||
layer = new U.Polyline(this.map, latlngs, {
|
||||
datalayer: this.datalayer,
|
||||
}).addTo(this.datalayer)
|
||||
layer.mergeShapes()
|
||||
layer.disableEdit()
|
||||
assert.deepEqual(layer.getLatLngs(), [
|
||||
L.latLng([0, 0]),
|
||||
L.latLng([0, 1]),
|
||||
L.latLng([0, 2]),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
describe('L.Util', function () {
|
||||
describe('#TextColorFromBackgroundColor', function () {
|
||||
it('should output white for black', function () {
|
||||
document.body.style.backgroundColor = 'black'
|
||||
assert.equal(L.DomUtil.TextColorFromBackgroundColor(document.body), '#ffffff')
|
||||
})
|
||||
|
||||
it('should output white for brown', function () {
|
||||
document.body.style.backgroundColor = 'brown'
|
||||
assert.equal(L.DomUtil.TextColorFromBackgroundColor(document.body), '#ffffff')
|
||||
})
|
||||
|
||||
it('should output black for white', function () {
|
||||
document.body.style.backgroundColor = 'white'
|
||||
assert.equal(L.DomUtil.TextColorFromBackgroundColor(document.body), '#000000')
|
||||
})
|
||||
|
||||
it('should output black for tan', function () {
|
||||
document.body.style.backgroundColor = 'tan'
|
||||
assert.equal(L.DomUtil.TextColorFromBackgroundColor(document.body), '#000000')
|
||||
})
|
||||
|
||||
it('should output black by default', function () {
|
||||
document.body.style.backgroundColor = 'transparent'
|
||||
assert.equal(L.DomUtil.TextColorFromBackgroundColor(document.body), '#000000')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,455 +0,0 @@
|
|||
window.assert = chai.assert
|
||||
window.expect = chai.expect
|
||||
|
||||
var qs = function (selector, element) {
|
||||
return (element || document).querySelector(selector)
|
||||
}
|
||||
var qsa = function (selector) {
|
||||
return document.querySelectorAll(selector)
|
||||
}
|
||||
var qst = function (text, parent) {
|
||||
// find element by its text content
|
||||
var r = document.evaluate(
|
||||
"descendant::*[contains(text(),'" + text + "')]",
|
||||
parent || qs('#map'),
|
||||
null,
|
||||
XPathResult.UNORDERED_NODE_ITERATOR_TYPE,
|
||||
null
|
||||
),
|
||||
count = 0
|
||||
while (r.iterateNext()) console.log(++count)
|
||||
return count
|
||||
}
|
||||
happen.at = function (what, x, y, props) {
|
||||
this.once(
|
||||
document.elementFromPoint(x, y),
|
||||
L.Util.extend(
|
||||
{
|
||||
type: what,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
screenX: x,
|
||||
screenY: y,
|
||||
which: 1,
|
||||
button: 0,
|
||||
},
|
||||
props || {}
|
||||
)
|
||||
)
|
||||
}
|
||||
var resetMap = function () {
|
||||
var mapElement = qs('#map')
|
||||
mapElement.innerHTML = 'Done'
|
||||
delete mapElement._leaflet_id
|
||||
document.body.className = ''
|
||||
}
|
||||
var enableEdit = function () {
|
||||
happen.click(qs('div.leaflet-control-edit-enable button'))
|
||||
}
|
||||
var disableEdit = function () {
|
||||
happen.click(qs('.leaflet-control-edit-disable'))
|
||||
}
|
||||
var clickSave = function () {
|
||||
happen.click(qs('.leaflet-control-edit-save'))
|
||||
}
|
||||
var clickCancel = function () {
|
||||
var _confirm = window.confirm
|
||||
window.confirm = function (text) {
|
||||
return true
|
||||
}
|
||||
happen.click(qs('button.leaflet-control-edit-cancel'))
|
||||
happen.once(document.body, { type: 'keypress', keyCode: 13 })
|
||||
window.confirm = _confirm
|
||||
}
|
||||
var changeInputValue = function (input, value) {
|
||||
input.value = value
|
||||
happen.once(input, { type: 'input' })
|
||||
happen.once(input, { type: 'blur' })
|
||||
}
|
||||
var changeSelectValue = function (path_or_select, value) {
|
||||
if (typeof path_or_select === 'string') path_or_select = qs(path_or_select)
|
||||
var found = false
|
||||
for (var i = 0; i < path_or_select.length; i++) {
|
||||
if (path_or_select.options[i].value === value) {
|
||||
path_or_select.options[i].selected = true
|
||||
found = true
|
||||
}
|
||||
}
|
||||
happen.once(path_or_select, { type: 'change' })
|
||||
if (!found)
|
||||
throw new Error('Value ' + value + 'not found in select ' + path_or_select)
|
||||
return path_or_select
|
||||
}
|
||||
var cleanAlert = function () {
|
||||
L.DomUtil.removeClass(qs('#map'), 'umap-alert')
|
||||
L.DomUtil.get('umap-alert-container').innerHTML = ''
|
||||
UI_ALERT_ID = null // Prevent setTimeout to be called
|
||||
}
|
||||
var defaultDatalayerData = function (custom) {
|
||||
var _default = {
|
||||
iconClass: 'Default',
|
||||
name: 'Elephants',
|
||||
displayOnLoad: true,
|
||||
id: 62,
|
||||
pictogram_url: null,
|
||||
weight: null,
|
||||
fillColor: '',
|
||||
color: '',
|
||||
stroke: true,
|
||||
smoothFactor: null,
|
||||
dashArray: '',
|
||||
fill: true,
|
||||
}
|
||||
return L.extend({}, _default, custom)
|
||||
}
|
||||
|
||||
function initMap(options) {
|
||||
default_options = {
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
umap_id: 42,
|
||||
datalayers: [],
|
||||
urls: {
|
||||
map: '/map/{slug}_{pk}',
|
||||
datalayer_view: '/datalayer/{pk}/',
|
||||
map_update: '/map/{map_id}/update/settings/',
|
||||
map_old_url: '/map/{username}/{slug}/',
|
||||
map_clone: '/map/{map_id}/update/clone/',
|
||||
map_short_url: '/m/{pk}/',
|
||||
map_anonymous_edit_url: '/map/anonymous-edit/{signature}',
|
||||
map_new: '/map/new/',
|
||||
datalayer_update: '/map/{map_id}/datalayer/update/{pk}/',
|
||||
map_delete: '/map/{map_id}/update/delete/',
|
||||
map_create: '/map/create/',
|
||||
logout: '/logout/',
|
||||
datalayer_create: '/map/{map_id}/datalayer/create/',
|
||||
login_popup_end: '/login/popupd/',
|
||||
login: '/login/',
|
||||
datalayer_delete: '/map/{map_id}/datalayer/delete/{pk}/',
|
||||
datalayer_versions: '/map/{map_id}/datalayer/{pk}/versions/',
|
||||
datalayer_version: '/datalayer/{pk}/{name}',
|
||||
pictogram_list_json: '/pictogram/json/',
|
||||
map_update_permissions: '/map/{map_id}/update/permissions/',
|
||||
map_download: '/map/{map_id}/download/',
|
||||
},
|
||||
default_iconUrl: '../src/img/marker.svg',
|
||||
zoom: 6,
|
||||
share_statuses: [
|
||||
[1, 'Tout le monde (public)'],
|
||||
[2, 'Quiconque a le lien'],
|
||||
[3, 'Éditeurs uniquement'],
|
||||
],
|
||||
tilelayers: [
|
||||
{
|
||||
attribution: '\u00a9 OSM Contributors',
|
||||
name: 'OpenStreetMap',
|
||||
url_template: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
|
||||
minZoom: 0,
|
||||
maxZoom: 18,
|
||||
id: 1,
|
||||
selected: true,
|
||||
},
|
||||
{
|
||||
attribution: 'HOT and friends',
|
||||
name: 'HOT OSM-fr server',
|
||||
url_template: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
|
||||
rank: 99,
|
||||
minZoom: 0,
|
||||
maxZoom: 20,
|
||||
id: 2,
|
||||
},
|
||||
],
|
||||
tilelayer: {
|
||||
attribution: 'HOT and friends',
|
||||
name: 'HOT OSM-fr server',
|
||||
url_template: 'http://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
|
||||
rank: 99,
|
||||
minZoom: 0,
|
||||
maxZoom: 20,
|
||||
id: 2,
|
||||
},
|
||||
licences: {
|
||||
'No licence set': {
|
||||
url: '',
|
||||
name: 'No licence set',
|
||||
},
|
||||
'Licence ouverte/Open Licence': {
|
||||
url: 'http://www.data.gouv.fr/Licence-Ouverte-Open-Licence',
|
||||
name: 'Licence ouverte/Open Licence',
|
||||
},
|
||||
'WTFPL': {
|
||||
url: 'http://www.wtfpl.net/',
|
||||
name: 'WTFPL',
|
||||
},
|
||||
'ODbl': {
|
||||
url: 'http://opendatacommons.org/licenses/odbl/',
|
||||
name: 'ODbl',
|
||||
},
|
||||
},
|
||||
name: 'name of the map',
|
||||
description: 'The description of the map',
|
||||
locale: 'en',
|
||||
editMode: 'advanced',
|
||||
moreControl: true,
|
||||
scaleControl: true,
|
||||
miniMap: false,
|
||||
datalayersControl: true,
|
||||
displayCaptionOnLoad: false,
|
||||
displayPopupFooter: false,
|
||||
displayDataBrowserOnLoad: false,
|
||||
permissions: {
|
||||
share_status: 1,
|
||||
owner: {
|
||||
id: 1,
|
||||
name: 'ybon',
|
||||
url: '/en/user/ybon/',
|
||||
},
|
||||
editors: [],
|
||||
},
|
||||
user: {
|
||||
id: 1,
|
||||
name: 'foofoo',
|
||||
url: '/en/me',
|
||||
},
|
||||
},
|
||||
}
|
||||
options = options || {}
|
||||
options.properties = L.extend({}, default_options.properties, options)
|
||||
options.geometry = {
|
||||
type: 'Point',
|
||||
coordinates: [5.0592041015625, 52.05924589011585],
|
||||
}
|
||||
return new U.Map('map', options)
|
||||
}
|
||||
|
||||
var RESPONSES = {
|
||||
datalayer62_GET: {
|
||||
crs: null,
|
||||
type: 'FeatureCollection',
|
||||
_umap_options: defaultDatalayerData(),
|
||||
features: [
|
||||
{
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [-0.274658203125, 52.57634993749885],
|
||||
},
|
||||
type: 'Feature',
|
||||
id: 1807,
|
||||
properties: { _umap_options: { color: 'OliveDrab' }, name: 'test' },
|
||||
},
|
||||
{
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[-0.5712890625, 54.47642158429295],
|
||||
[0.439453125, 54.610254981579146],
|
||||
[1.724853515625, 53.44880683542759],
|
||||
[4.163818359375, 53.98839506479995],
|
||||
[5.306396484375, 53.533778184257805],
|
||||
[6.591796875, 53.70971358510174],
|
||||
[7.042236328124999, 53.35055131839989],
|
||||
],
|
||||
},
|
||||
type: 'Feature',
|
||||
id: 20,
|
||||
properties: { _umap_options: { fill: false, opacity: 0.6 }, name: 'test' },
|
||||
},
|
||||
{
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[11.25, 53.585983654559804],
|
||||
[10.1513671875, 52.9751081817353],
|
||||
[12.689208984375, 52.16719363541221],
|
||||
[14.084472656249998, 53.199451902831555],
|
||||
[12.63427734375, 53.61857936489517],
|
||||
[11.25, 53.585983654559804],
|
||||
[11.25, 53.585983654559804],
|
||||
],
|
||||
],
|
||||
},
|
||||
type: 'Feature',
|
||||
id: 76,
|
||||
properties: { name: 'name poly' },
|
||||
},
|
||||
],
|
||||
},
|
||||
// This one is non browsable
|
||||
datalayer63_GET: {
|
||||
crs: null,
|
||||
type: 'FeatureCollection',
|
||||
_umap_options: defaultDatalayerData({ id: 63, browsable: false }),
|
||||
features: [
|
||||
{
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[5.545478, 45.068383],
|
||||
[5.545907, 45.067277],
|
||||
[5.548439, 45.067565],
|
||||
[5.552516, 45.06752],
|
||||
[5.553288, 45.068217],
|
||||
[5.549405, 45.069247],
|
||||
[5.548224, 45.071005],
|
||||
[5.545907, 45.071096],
|
||||
[5.545478, 45.068383],
|
||||
],
|
||||
],
|
||||
},
|
||||
type: 'Feature',
|
||||
id: 76,
|
||||
properties: { name: 'non browsable 1' },
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
_umap_options: {
|
||||
color: 'SteelBlue',
|
||||
},
|
||||
name: 'non browsable 2',
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[5.550542, 45.071717],
|
||||
[5.548182, 45.071051],
|
||||
[5.549426, 45.069232],
|
||||
[5.553331, 45.068171],
|
||||
[5.554812, 45.070869],
|
||||
[5.553396, 45.072384],
|
||||
[5.550542, 45.071717],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// This one is not shown at load
|
||||
datalayer64_GET: {
|
||||
crs: null,
|
||||
type: 'FeatureCollection',
|
||||
_umap_options: defaultDatalayerData({
|
||||
name: 'hidden',
|
||||
id: 64,
|
||||
displayOnLoad: false,
|
||||
}),
|
||||
features: [
|
||||
{
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[5.545478, 45.068383],
|
||||
[5.545907, 45.067277],
|
||||
[5.548439, 45.067565],
|
||||
[5.552516, 45.06752],
|
||||
[5.553288, 45.068217],
|
||||
[5.549405, 45.069247],
|
||||
[5.548224, 45.071005],
|
||||
[5.545907, 45.071096],
|
||||
[5.545478, 45.068383],
|
||||
],
|
||||
],
|
||||
},
|
||||
type: 'Feature',
|
||||
id: 76,
|
||||
properties: { name: 'not shown at load 1' },
|
||||
},
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
_umap_options: {
|
||||
color: 'AliceBlue',
|
||||
},
|
||||
name: 'not shown at load 2',
|
||||
},
|
||||
geometry: {
|
||||
type: 'Polygon',
|
||||
coordinates: [
|
||||
[
|
||||
[5.550542, 45.071717],
|
||||
[5.548182, 45.071051],
|
||||
[5.549426, 45.069232],
|
||||
[5.553331, 45.068171],
|
||||
[5.554812, 45.070869],
|
||||
[5.553396, 45.072384],
|
||||
[5.550542, 45.071717],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
var kml_example =
|
||||
'<?xml version="1.0" encoding="UTF-8"?>' +
|
||||
'<kml xmlns="http://www.opengis.net/kml/2.2">' +
|
||||
'<Placemark>' +
|
||||
'<name>Simple point</name>' +
|
||||
'<description>Here is a simple description.</description>' +
|
||||
'<Point>' +
|
||||
'<coordinates>-122.0822035425683,37.42228990140251,0</coordinates>' +
|
||||
'</Point>' +
|
||||
'</Placemark>' +
|
||||
'<Placemark>' +
|
||||
'<name>Simple path</name>' +
|
||||
'<description>Simple description</description>' +
|
||||
'<LineString>' +
|
||||
'<coordinates>-112.2550785337791,36.07954952145647,2357 -112.2549277039738,36.08117083492122,2357 -112.2552505069063,36.08260761307279,2357</coordinates>' +
|
||||
'</LineString>' +
|
||||
'</Placemark>' +
|
||||
'<Placemark>' +
|
||||
'<name>Simple polygon</name>' +
|
||||
'<description>A description.</description>' +
|
||||
'<Polygon>' +
|
||||
'<outerBoundaryIs>' +
|
||||
'<LinearRing>' +
|
||||
'<coordinates>' +
|
||||
' -77.05788457660967,38.87253259892824,100 ' +
|
||||
' -77.05465973756702,38.87291016281703,100 ' +
|
||||
' -77.05315536854791,38.87053267794386,100 ' +
|
||||
' -77.05788457660967,38.87253259892824,100 ' +
|
||||
'</coordinates>' +
|
||||
'</LinearRing>' +
|
||||
'</outerBoundaryIs>' +
|
||||
'</Polygon>' +
|
||||
'</Placemark>' +
|
||||
'</kml>'
|
||||
|
||||
var gpx_example =
|
||||
'<gpx' +
|
||||
' version="1.1"' +
|
||||
' creator="GPSBabel - http://www.gpsbabel.org"' +
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
|
||||
' xmlns="http://www.topografix.com/GPX/1/1"' +
|
||||
' xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' +
|
||||
' <wpt lat="45.44283" lon="-121.72904"><ele>1374</ele><name>Simple Point</name><desc>Simple description</desc></wpt>' +
|
||||
' <trk>' +
|
||||
' <name>Simple path</name>' +
|
||||
' <desc>Simple description</desc>' +
|
||||
' <trkseg>' +
|
||||
' <trkpt lat="45.4431641" lon="-121.7295456"></trkpt>' +
|
||||
' <trkpt lat="45.4428615" lon="-121.7290800"></trkpt>' +
|
||||
' <trkpt lat="45.4425697" lon="-121.7279085"></trkpt>' +
|
||||
' </trkseg>' +
|
||||
' </trk>' +
|
||||
'</gpx>'
|
||||
|
||||
var csv_example =
|
||||
'Foo,Latitude,Longitude,title,description\n' +
|
||||
'bar,41.34,122.86,a point somewhere,the description of this point'
|
||||
|
||||
// Make Sinon log readable
|
||||
sinon.format = function (what) {
|
||||
if (typeof what === 'object') {
|
||||
return JSON.stringify(what, null, 4)
|
||||
} else if (typeof what === 'undefined') {
|
||||
return ''
|
||||
} else {
|
||||
return what.toString()
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Umap front Tests</title>
|
||||
<meta charset="utf-8" />
|
||||
<script type="module" src="../js/modules/leaflet-configure.js" defer></script>
|
||||
<script type="module" src="../js/modules/global.js" defer></script>
|
||||
|
||||
<script src="../vendors/editable/Path.Drag.js" defer></script>
|
||||
<script src="../vendors/editable/Leaflet.Editable.js" defer></script>
|
||||
<script src="../vendors/hash/leaflet-hash.js" defer></script>
|
||||
<script src="../vendors/editinosm/Leaflet.EditInOSM.js" defer></script>
|
||||
<script src="../vendors/minimap/Control.MiniMap.min.js" defer></script>
|
||||
<script src="../vendors/csv2geojson/csv2geojson.js" defer></script>
|
||||
<script src="../vendors/togeojson/togeojson.umd.js" defer></script>
|
||||
<script src="../vendors/osmtogeojson/osmtogeojson.js" defer></script>
|
||||
<script src="../vendors/loading/Control.Loading.js" defer></script>
|
||||
<script src="../vendors/markercluster/leaflet.markercluster.js" defer></script>
|
||||
<script src="../vendors/contextmenu/leaflet.contextmenu.min.js" defer></script>
|
||||
<script src="../vendors/photon/leaflet.photon.js" defer></script>
|
||||
<script src="../vendors/georsstogeojson/GeoRSSToGeoJSON.js" defer></script>
|
||||
<script src="../vendors/heat/leaflet-heat.js" defer></script>
|
||||
<script src="../vendors/fullscreen/Leaflet.fullscreen.min.js" defer></script>
|
||||
<script src="../vendors/toolbar/leaflet.toolbar.js" defer></script>
|
||||
<script src="../vendors/formbuilder/Leaflet.FormBuilder.js" defer></script>
|
||||
<script src="../vendors/measurable/Leaflet.Measurable.js" defer></script>
|
||||
<script src="../vendors/togpx/togpx.js" defer></script>
|
||||
<script src="../vendors/iconlayers/iconLayers.js" defer></script>
|
||||
<script src="../vendors/tokml/tokml.js" defer></script>
|
||||
<script src="../vendors/locatecontrol/L.Control.Locate.min.js" defer></script>
|
||||
<script src="../vendors/colorbrewer/colorbrewer.js" defer></script>
|
||||
<script src="../vendors/simple-statistics/simple-statistics.min.js" defer></script>
|
||||
|
||||
<script src="../js/umap.core.js" defer></script>
|
||||
<script src="../js/umap.autocomplete.js" defer></script>
|
||||
<script src="../js/umap.popup.js" defer></script>
|
||||
<script src="../js/umap.forms.js" defer></script>
|
||||
<script src="../js/umap.icon.js" defer></script>
|
||||
<script src="../js/umap.features.js" defer></script>
|
||||
<script src="../js/umap.permissions.js" defer></script>
|
||||
<script src="../js/umap.datalayer.permissions.js" defer></script>
|
||||
<script src="../js/umap.layer.js" defer></script>
|
||||
<script src="../js/umap.controls.js" defer></script>
|
||||
<script src="../js/umap.slideshow.js" defer></script>
|
||||
<script src="../js/umap.tableeditor.js" defer></script>
|
||||
<script src="../js/umap.importer.js" defer></script>
|
||||
<script src="../js/umap.share.js" defer></script>
|
||||
<script src="../js/umap.js" defer></script>
|
||||
<script src="../js/umap.ui.js" defer></script>
|
||||
<script src="../js/components/fragment.js" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="../vendors/leaflet/leaflet.css" />
|
||||
<link rel="stylesheet" href="../vendors/markercluster/MarkerCluster.css" />
|
||||
<link rel="stylesheet" href="../vendors/markercluster/MarkerCluster.Default.css" />
|
||||
<link rel="stylesheet" href="../vendors/editinosm/Leaflet.EditInOSM.css" />
|
||||
<link rel="stylesheet" href="../vendors/minimap/Control.MiniMap.min.css" />
|
||||
<link rel="stylesheet" href="../vendors/contextmenu/leaflet.contextmenu.min.css" />
|
||||
<link rel="stylesheet" href="../vendors/toolbar/leaflet.toolbar.css" />
|
||||
<link rel="stylesheet" href="../vendors/measurable/Leaflet.Measurable.css" />
|
||||
<link rel="stylesheet" href="../vendors/fullscreen/leaflet.fullscreen.css" />
|
||||
<link rel="stylesheet" href="../vendors/locatecontrol/L.Control.Locate.min.css" />
|
||||
<link rel="stylesheet" href="../vendors/iconlayers/iconLayers.css" />
|
||||
|
||||
<link rel="stylesheet" href="../../umap/vars.css" />
|
||||
<link rel="stylesheet" href="../../umap/font.css" />
|
||||
<link rel="stylesheet" href="../../umap/base.css" />
|
||||
<link rel="stylesheet" href="../../umap/content.css" />
|
||||
<link rel="stylesheet" href="../../umap/nav.css" />
|
||||
<link rel="stylesheet" href="../../umap/map.css" />
|
||||
<link rel="stylesheet" href="../../umap/theme.css" />
|
||||
|
||||
<script src="../../../../node_modules/sinon/pkg/sinon.js"></script>
|
||||
<script src="../../../../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../../../../node_modules/chai/chai.js"></script>
|
||||
<script src="../../../../node_modules/happen/happen.js"></script>
|
||||
<link rel="stylesheet" href="../../../../node_modules/mocha/mocha.css" />
|
||||
<script type="module">
|
||||
import fetchMock from '../../../../node_modules/fetch-mock/esm/client.js';
|
||||
window.fetchMock = fetchMock
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
mocha.setup({
|
||||
ui: 'bdd',
|
||||
bail: window.location.search.indexOf('failfast') !== -1,
|
||||
ignoreLeaks: true,
|
||||
})
|
||||
chai.config.includeStack = true
|
||||
</script>
|
||||
<script src="./_pre.js" defer></script>
|
||||
<script src="./Map.js" defer></script>
|
||||
<script src="./Feature.js" defer></script>
|
||||
<script src="./Marker.js" defer></script>
|
||||
<script src="./Polyline.js" defer></script>
|
||||
<script src="./Polygon.js" defer></script>
|
||||
<script src="./Util.js" defer></script>
|
||||
<script type="module" src="./URLs.js" defer></script>
|
||||
<style type="text/css">
|
||||
#mocha {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10000;
|
||||
background-color: white;
|
||||
box-shadow: 0px 0px 8px 0px black;
|
||||
overflow-y: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#mocha-stats {
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<div id="map"></div>
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
var runner = (window.mochaPhantomJS || window.mocha).run(function (failures) {
|
||||
if (window.location.search.indexOf('debug') === -1)
|
||||
qs('#mocha').style.display = 'block'
|
||||
console.log(failures)
|
||||
})
|
||||
if (window.location.search.indexOf('debug') !== -1) {
|
||||
runner.on('fail', function (test, err) {
|
||||
console.log(test.title, test.err)
|
||||
console.log(test.err.expected, test.err.actual)
|
||||
console.log(test.err.stack)
|
||||
})
|
||||
sinon.log = function (message) {
|
||||
console.log(message)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -44,6 +44,7 @@
|
|||
--small-box-padding: 4px;
|
||||
--box-margin: 14px;
|
||||
--text-margin: 7px;
|
||||
--dialog-width: 40vw;
|
||||
|
||||
/* z-indexes (leaflet CSS sets the map at 400 by default) */
|
||||
--zindex-alert: 500;
|
||||
|
|
|
@ -5,14 +5,22 @@
|
|||
<div class="wrapper search_wrapper">
|
||||
<div class="row">
|
||||
<form action="{% firstof action search_url %}" method="get">
|
||||
<div class="col two-third mwide">
|
||||
<div class="col half mwide">
|
||||
<input name="q"
|
||||
type="search"
|
||||
placeholder="{% firstof placeholder default_placeholder %}"
|
||||
aria-label="{% firstof placeholder default_placeholder %}"
|
||||
value="{{ request.GET.q|default:"" }}" />
|
||||
</div>
|
||||
<div class="col third mwide">
|
||||
<div class="col quarter mwide">
|
||||
<select name="tags">
|
||||
<option value="">{% trans "Any category" %}</option>
|
||||
{% for value, label in UMAP_TAGS %}
|
||||
<option value="{{ value }}" {% if request.GET.tags == value %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col quarter mwide">
|
||||
<input type="submit" value="{% trans "Search" %}" class="neutral" />
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -226,3 +226,18 @@ def test_hover_tooltip_setting_should_be_persistent(live_server, map, page):
|
|||
- text: always never on hover
|
||||
""")
|
||||
expect(page.locator(".umap-field-showLabel input[value=null]")).to_be_checked()
|
||||
|
||||
|
||||
def test_can_edit_map_tags(live_server, map, page):
|
||||
map.settings["properties"]["tags"] = ["art"]
|
||||
map.edit_status = Map.ANONYMOUS
|
||||
map.save()
|
||||
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||
page.get_by_role("button", name="Edit map name and caption").click()
|
||||
page.get_by_text("Tags").click()
|
||||
expect(page.get_by_label("Art and Culture")).to_be_checked()
|
||||
page.get_by_label("Bike").check()
|
||||
with page.expect_response(re.compile("./update/settings/.*")):
|
||||
page.get_by_role("button", name="Save").click()
|
||||
saved = Map.objects.get(pk=map.pk)
|
||||
assert saved.tags == ["art", "bike"]
|
||||
|
|
|
@ -486,3 +486,27 @@ def test_cannot_search_deleted_map(client, map):
|
|||
url = reverse("search")
|
||||
response = client.get(url + "?q=Blé")
|
||||
assert "Blé dur" not in response.content.decode()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_filter_by_tag(client, map):
|
||||
# Very basic search, that do not deal with accent nor case.
|
||||
# See install.md for how to have a smarter dict + index.
|
||||
map.name = "Blé dur"
|
||||
map.tags = ["bike"]
|
||||
map.save()
|
||||
url = reverse("search")
|
||||
response = client.get(url + "?tags=bike")
|
||||
assert "Blé dur" in response.content.decode()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_can_combine_search_and_filter(client, map):
|
||||
# Very basic search, that do not deal with accent nor case.
|
||||
# See install.md for how to have a smarter dict + index.
|
||||
map.name = "Blé dur"
|
||||
map.tags = ["bike"]
|
||||
map.save()
|
||||
url = reverse("search")
|
||||
response = client.get(url + "?q=dur&tags=bike")
|
||||
assert "Blé dur" in response.content.decode()
|
||||
|
|
|
@ -334,12 +334,18 @@ class TeamMaps(PaginatorMixin, DetailView):
|
|||
class SearchMixin:
|
||||
def get_search_queryset(self, **kwargs):
|
||||
q = self.request.GET.get("q")
|
||||
tags = [t for t in self.request.GET.getlist("tags") if t]
|
||||
qs = Map.objects.all()
|
||||
if q:
|
||||
vector = SearchVector("name", config=settings.UMAP_SEARCH_CONFIGURATION)
|
||||
query = SearchQuery(
|
||||
q, config=settings.UMAP_SEARCH_CONFIGURATION, search_type="websearch"
|
||||
)
|
||||
return Map.objects.annotate(search=vector).filter(search=query)
|
||||
qs = qs.annotate(search=vector).filter(search=query)
|
||||
if tags:
|
||||
qs = qs.filter(tags__contains=tags)
|
||||
if q or tags:
|
||||
return qs
|
||||
|
||||
|
||||
class Search(PaginatorMixin, TemplateView, PublicMapsMixin, SearchMixin):
|
||||
|
|