Merge pull request #1395 from umap-project/picto-ui
Refactor icon selector: use tabs, make options more explicit
This commit is contained in:
commit
0db7f377c3
9 changed files with 440 additions and 100 deletions
|
@ -194,6 +194,7 @@ input[type="submit"] {
|
||||||
.dark a {
|
.dark a {
|
||||||
color: #eeeeec;
|
color: #eeeeec;
|
||||||
}
|
}
|
||||||
|
button.flat,
|
||||||
[type="button"].flat,
|
[type="button"].flat,
|
||||||
.dark [type="button"].flat {
|
.dark [type="button"].flat {
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -536,9 +537,30 @@ i.info {
|
||||||
margin-top: -8px;
|
margin-top: -8px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
}
|
}
|
||||||
.umap-pictogram-grid {
|
.pictogram-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
justify-content: space-around;
|
||||||
|
font-size: 1.2em;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.pictogram-tabs button {
|
||||||
|
padding: 10px;
|
||||||
|
color: #fff;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.pictogram-tabs .on {
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
}
|
||||||
|
.umap-pictogram-category h6 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
}
|
||||||
|
.umap-pictogram-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, 30px);
|
||||||
|
justify-content: space-between;
|
||||||
|
grid-gap: 5px;
|
||||||
}
|
}
|
||||||
.umap-pictogram-choice {
|
.umap-pictogram-choice {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
|
@ -548,17 +570,20 @@ i.info {
|
||||||
background-color: #999;
|
background-color: #999;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
margin-right: 5px;
|
display: block;
|
||||||
}
|
}
|
||||||
.umap-pictogram-choice img {
|
.umap-pictogram-choice img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
max-width: 24px;
|
max-width: 24px;
|
||||||
}
|
}
|
||||||
.umap-pictogram-choice:hover,
|
.umap-pictogram-choice:hover,
|
||||||
.umap-pictogram-choice.selected,
|
|
||||||
.umap-color-picker span:hover {
|
.umap-color-picker span:hover {
|
||||||
box-shadow: 0 0 4px 0 black;
|
background-color: #bebebe;
|
||||||
}
|
}
|
||||||
|
.umap-pictogram-choice.selected {
|
||||||
|
box-shadow: inset 0 0 0 1px #e9e9e9;
|
||||||
|
}
|
||||||
|
|
||||||
.umap-pictogram-choice .leaflet-marker-icon {
|
.umap-pictogram-choice .leaflet-marker-icon {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 30px;
|
left: 30px;
|
||||||
|
|
|
@ -287,6 +287,13 @@ L.Util.copyToClipboard = function (textToCopy) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
L.Util.normalize = function (s) {
|
||||||
|
return (s || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
L.DomUtil.add = (tagName, className, container, content) => {
|
L.DomUtil.add = (tagName, className, container, content) => {
|
||||||
const el = L.DomUtil.create(tagName, className, container)
|
const el = L.DomUtil.create(tagName, className, container)
|
||||||
if (content) {
|
if (content) {
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
L.FormBuilder.Element.include({
|
L.FormBuilder.Element.include({
|
||||||
|
undefine: function () {
|
||||||
|
L.DomUtil.addClass(this.wrapper, 'undefined')
|
||||||
|
this.clear()
|
||||||
|
this.sync()
|
||||||
|
},
|
||||||
|
|
||||||
getParentNode: function () {
|
getParentNode: function () {
|
||||||
if (this.options.wrapper) {
|
if (this.options.wrapper) {
|
||||||
return L.DomUtil.create(
|
return L.DomUtil.create(
|
||||||
|
@ -29,15 +35,10 @@ L.FormBuilder.Element.include({
|
||||||
},
|
},
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
L.DomEvent.on(
|
L.DomEvent.on(undefine, 'click', L.DomEvent.stop).on(
|
||||||
undefine,
|
undefine,
|
||||||
'click',
|
'click',
|
||||||
function (e) {
|
this.undefine,
|
||||||
L.DomEvent.stop(e)
|
|
||||||
L.DomUtil.addClass(this.wrapper, 'undefined')
|
|
||||||
this.clear()
|
|
||||||
this.sync()
|
|
||||||
},
|
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -524,48 +525,111 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
|
||||||
|
|
||||||
build: function () {
|
build: function () {
|
||||||
L.FormBuilder.BlurInput.prototype.build.call(this)
|
L.FormBuilder.BlurInput.prototype.build.call(this)
|
||||||
// Try to guess if the icon content has been customized, and if yes
|
this.buttons = L.DomUtil.create('div', '', this.parentNode)
|
||||||
// directly display the field
|
this.tabs = L.DomUtil.create('div', 'pictogram-tabs', this.parentNode)
|
||||||
this.input.type = this.value() && !this.value().startsWith('/') ? 'text' : 'hidden'
|
this.body = L.DomUtil.create('div', 'umap-pictogram-body', this.parentNode)
|
||||||
this.input.placeholder = L._('Symbol or url')
|
this.footer = L.DomUtil.create('div', '', this.parentNode)
|
||||||
this.buttonsContainer = L.DomUtil.create('div', '')
|
|
||||||
this.pictogramsContainer = L.DomUtil.create('div', 'umap-pictogram-list')
|
|
||||||
L.DomUtil.before(this.input, this.buttonsContainer)
|
|
||||||
L.DomUtil.before(this.input, this.pictogramsContainer)
|
|
||||||
this.udpatePreview()
|
this.udpatePreview()
|
||||||
this.on('define', this.fetchIconList)
|
this.on('define', this.onDefine)
|
||||||
},
|
},
|
||||||
|
|
||||||
isUrl: function () {
|
onDefine: function () {
|
||||||
return this.value() && this.value().indexOf('/') !== -1
|
this.buttons.innerHTML = ''
|
||||||
|
this.footer.innerHTML = ''
|
||||||
|
this.buildTabs()
|
||||||
|
const value = this.value()
|
||||||
|
if (!value || value.startsWith('/')) this.showSymbolsTab()
|
||||||
|
else if (value.startsWith('http')) this.showURLTab()
|
||||||
|
else this.showCharsTab()
|
||||||
|
const closeButton = L.DomUtil.createButton(
|
||||||
|
'button action-button',
|
||||||
|
this.footer,
|
||||||
|
L._('Close'),
|
||||||
|
function (e) {
|
||||||
|
this.body.innerHTML = ''
|
||||||
|
this.tabs.innerHTML = ''
|
||||||
|
this.footer.innerHTML = ''
|
||||||
|
if (this.isDefault()) this.undefine(e)
|
||||||
|
else this.udpatePreview()
|
||||||
|
},
|
||||||
|
this
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
buildTabs: function () {
|
||||||
|
this.tabs.innerHTML = ''
|
||||||
|
const symbol = L.DomUtil.add(
|
||||||
|
'button',
|
||||||
|
'flat tab-symbols',
|
||||||
|
this.tabs,
|
||||||
|
L._('Symbol')
|
||||||
|
),
|
||||||
|
char = L.DomUtil.add(
|
||||||
|
'button',
|
||||||
|
'flat tab-chars',
|
||||||
|
this.tabs,
|
||||||
|
L._('Emoji & Character')
|
||||||
|
)
|
||||||
|
url = L.DomUtil.add('button', 'flat tab-url', this.tabs, L._('URL'))
|
||||||
|
L.DomEvent.on(symbol, 'click', L.DomEvent.stop).on(
|
||||||
|
symbol,
|
||||||
|
'click',
|
||||||
|
this.showSymbolsTab,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
L.DomEvent.on(char, 'click', L.DomEvent.stop).on(
|
||||||
|
char,
|
||||||
|
'click',
|
||||||
|
this.showCharsTab,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
L.DomEvent.on(url, 'click', L.DomEvent.stop).on(url, 'click', this.showURLTab, this)
|
||||||
|
},
|
||||||
|
|
||||||
|
openTab: function (name) {
|
||||||
|
const els = this.tabs.querySelectorAll('button')
|
||||||
|
for (let el of els) {
|
||||||
|
L.DomUtil.removeClass(el, 'on')
|
||||||
|
}
|
||||||
|
let el = this.tabs.querySelector(`.tab-${name}`)
|
||||||
|
L.DomUtil.addClass(el, 'on')
|
||||||
|
this.body.innerHTML = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
isPath: function () {
|
||||||
|
const value = this.value()
|
||||||
|
return value && value.length && value.startsWith('/')
|
||||||
|
},
|
||||||
|
|
||||||
|
isRemoteUrl: function () {
|
||||||
|
const value = this.value()
|
||||||
|
return value && value.length && value.startsWith('http')
|
||||||
|
},
|
||||||
|
|
||||||
|
isImg: function () {
|
||||||
|
return this.isPath() || this.isRemoteUrl()
|
||||||
},
|
},
|
||||||
|
|
||||||
udpatePreview: function () {
|
udpatePreview: function () {
|
||||||
|
this.buttons.innerHTML = ''
|
||||||
|
if (this.isDefault()) return
|
||||||
if (!L.Util.hasVar(this.value())) {
|
if (!L.Util.hasVar(this.value())) {
|
||||||
// Do not try to render URL with variables
|
// Do not try to render URL with variables
|
||||||
if (this.isUrl()) {
|
const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons)
|
||||||
const img = L.DomUtil.create(
|
L.DomEvent.on(box, 'click', this.onDefine, this)
|
||||||
'img',
|
if (this.isImg()) {
|
||||||
'',
|
const img = L.DomUtil.create('img', '', box)
|
||||||
L.DomUtil.create('div', 'umap-pictogram-choice', this.buttonsContainer)
|
|
||||||
)
|
|
||||||
img.src = this.value()
|
img.src = this.value()
|
||||||
L.DomEvent.on(img, 'click', this.fetchIconList, this)
|
|
||||||
} else {
|
} else {
|
||||||
const el = L.DomUtil.create(
|
const el = L.DomUtil.create('span', '', box)
|
||||||
'span',
|
|
||||||
'',
|
|
||||||
L.DomUtil.create('div', 'umap-pictogram-choice', this.buttonsContainer)
|
|
||||||
)
|
|
||||||
el.textContent = this.value()
|
el.textContent = this.value()
|
||||||
L.DomEvent.on(el, 'click', this.fetchIconList, this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.button = L.DomUtil.createButton(
|
this.button = L.DomUtil.createButton(
|
||||||
'button action-button',
|
'button action-button',
|
||||||
this.buttonsContainer,
|
this.buttons,
|
||||||
this.value() ? L._('Change') : L._('Add'),
|
this.value() ? L._('Change') : L._('Add'),
|
||||||
this.fetchIconList,
|
this.onDefine,
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -573,64 +637,54 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
|
||||||
addIconPreview: function (pictogram, parent) {
|
addIconPreview: function (pictogram, parent) {
|
||||||
const baseClass = 'umap-pictogram-choice',
|
const baseClass = 'umap-pictogram-choice',
|
||||||
value = pictogram.src,
|
value = pictogram.src,
|
||||||
className = value === this.value() ? `${baseClass} selected` : baseClass,
|
search = L.Util.normalize(this.searchInput.value),
|
||||||
|
title = pictogram.attribution
|
||||||
|
? `${pictogram.name} — © ${pictogram.attribution}`
|
||||||
|
: pictogram.name
|
||||||
|
if (search && L.Util.normalize(title).indexOf(search) === -1) return
|
||||||
|
const className = value === this.value() ? `${baseClass} selected` : baseClass,
|
||||||
container = L.DomUtil.create('div', className, parent),
|
container = L.DomUtil.create('div', className, parent),
|
||||||
img = L.DomUtil.create('img', '', container)
|
img = L.DomUtil.create('img', '', container)
|
||||||
img.src = value
|
img.src = value
|
||||||
if (pictogram.name && pictogram.attribution) {
|
container.title = title
|
||||||
container.title = `${pictogram.name} — © ${pictogram.attribution}`
|
|
||||||
}
|
|
||||||
L.DomEvent.on(
|
L.DomEvent.on(
|
||||||
container,
|
container,
|
||||||
'click',
|
'click',
|
||||||
function (e) {
|
function (e) {
|
||||||
this.input.value = value
|
this.input.value = value
|
||||||
this.sync()
|
this.sync()
|
||||||
this.unselectAll(this.pictogramsContainer)
|
this.unselectAll(this.grid)
|
||||||
L.DomUtil.addClass(container, 'selected')
|
L.DomUtil.addClass(container, 'selected')
|
||||||
},
|
},
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
|
return true // Icon has been added (not filtered)
|
||||||
},
|
},
|
||||||
|
|
||||||
clear: function () {
|
clear: function () {
|
||||||
this.input.value = ''
|
this.input.value = ''
|
||||||
this.unselectAll(this.pictogramsContainer)
|
this.unselectAll(this.body)
|
||||||
this.sync()
|
this.sync()
|
||||||
this.pictogramsContainer.innerHTML = ''
|
this.body.innerHTML = ''
|
||||||
this.udpatePreview()
|
this.udpatePreview()
|
||||||
},
|
},
|
||||||
|
|
||||||
search: function (e) {
|
|
||||||
const icons = [...this.parentNode.querySelectorAll('.umap-pictogram-choice')],
|
|
||||||
search = this.searchInput.value.toLowerCase()
|
|
||||||
icons.forEach((el) => {
|
|
||||||
if (el.title.toLowerCase().indexOf(search) != -1) el.style.display = 'block'
|
|
||||||
else el.style.display = 'none'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
addCategory: function (category, items) {
|
addCategory: function (category, items) {
|
||||||
const parent = L.DomUtil.create(
|
const parent = L.DomUtil.create('div', 'umap-pictogram-category'),
|
||||||
'div',
|
|
||||||
'umap-pictogram-category',
|
|
||||||
this.pictogramsContainer
|
|
||||||
),
|
|
||||||
title = L.DomUtil.add('h6', '', parent, category),
|
title = L.DomUtil.add('h6', '', parent, category),
|
||||||
grid = L.DomUtil.create('div', 'umap-pictogram-grid', parent)
|
grid = L.DomUtil.create('div', 'umap-pictogram-grid', parent)
|
||||||
|
let status = false
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
this.addIconPreview(item, grid)
|
status = this.addIconPreview(item, grid) || status
|
||||||
}
|
}
|
||||||
|
if (status) this.grid.appendChild(parent)
|
||||||
},
|
},
|
||||||
|
|
||||||
buildIconList: function (data) {
|
buildSymbolsList: function () {
|
||||||
this.searchInput = L.DomUtil.create('input', '', this.pictogramsContainer)
|
this.grid.innerHTML = ''
|
||||||
this.searchInput.type = 'search'
|
|
||||||
this.searchInput.placeholder = L._('Search')
|
|
||||||
L.DomEvent.on(this.searchInput, 'input', this.search, this)
|
|
||||||
const categories = {}
|
const categories = {}
|
||||||
let category
|
let category
|
||||||
for (const props of data.pictogram_list) {
|
for (const props of this.pictogram_list) {
|
||||||
category = props.category || L._('Generic')
|
category = props.category || L._('Generic')
|
||||||
categories[category] = categories[category] || []
|
categories[category] = categories[category] || []
|
||||||
categories[category].push(props)
|
categories[category].push(props)
|
||||||
|
@ -641,39 +695,60 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
|
||||||
for (let [category, items] of sorted) {
|
for (let [category, items] of sorted) {
|
||||||
this.addCategory(category, items)
|
this.addCategory(category, items)
|
||||||
}
|
}
|
||||||
const closeButton = L.DomUtil.createButton(
|
|
||||||
'button action-button',
|
|
||||||
this.pictogramsContainer,
|
|
||||||
L._('Close'),
|
|
||||||
function (e) {
|
|
||||||
this.pictogramsContainer.innerHTML = ''
|
|
||||||
this.udpatePreview()
|
|
||||||
},
|
|
||||||
this
|
|
||||||
)
|
|
||||||
closeButton.style.display = 'block'
|
|
||||||
closeButton.style.clear = 'both'
|
|
||||||
|
|
||||||
const customButton = L.DomUtil.createButton(
|
|
||||||
'flat',
|
|
||||||
this.pictogramsContainer,
|
|
||||||
L._('Toggle direct input (advanced)'),
|
|
||||||
function (e) {
|
|
||||||
this.input.type = this.input.type === 'text' ? 'hidden' : 'text'
|
|
||||||
},
|
|
||||||
this
|
|
||||||
)
|
|
||||||
this.builder.map.help.button(customButton, 'formatIconSymbol')
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchIconList: function (e) {
|
isDefault: function () {
|
||||||
// Clean parent element before calling ajax, to prevent blinking
|
return !this.value() || this.value() === this.obj.getMap().options.default_iconUrl
|
||||||
this.pictogramsContainer.innerHTML = ''
|
},
|
||||||
this.buttonsContainer.innerHTML = ''
|
|
||||||
|
showSymbolsTab: function () {
|
||||||
|
this.openTab('symbols')
|
||||||
|
this.searchInput = L.DomUtil.create('input', '', this.body)
|
||||||
|
this.searchInput.type = 'search'
|
||||||
|
this.searchInput.placeholder = L._('Search')
|
||||||
|
this.grid = L.DomUtil.create('div', '', this.body)
|
||||||
|
L.DomEvent.on(this.searchInput, 'input', this.buildSymbolsList, this)
|
||||||
|
if (this.pictogram_list) {
|
||||||
|
this.buildSymbolsList()
|
||||||
|
} else {
|
||||||
this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, {
|
this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, {
|
||||||
callback: this.buildIconList,
|
callback: (data) => {
|
||||||
|
this.pictogram_list = data.pictogram_list
|
||||||
|
this.buildSymbolsList()
|
||||||
|
},
|
||||||
context: this,
|
context: this,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showCharsTab: function () {
|
||||||
|
this.openTab('chars')
|
||||||
|
const value = !this.isImg() ? this.value() : null
|
||||||
|
const input = this.buildInput(this.body, value)
|
||||||
|
input.placeholder = L._('Type char or paste emoji')
|
||||||
|
input.type = 'text'
|
||||||
|
},
|
||||||
|
|
||||||
|
showURLTab: function () {
|
||||||
|
this.openTab('url')
|
||||||
|
const value = this.isRemoteUrl() ? this.value() : null
|
||||||
|
const input = this.buildInput(this.body, value)
|
||||||
|
input.placeholder = L._('Add image URL')
|
||||||
|
input.type = 'url'
|
||||||
|
},
|
||||||
|
|
||||||
|
buildInput: function (parent, value) {
|
||||||
|
const input = L.DomUtil.create('input', 'blur', parent)
|
||||||
|
const button = L.DomUtil.create('span', 'button blur-button', parent)
|
||||||
|
if (value) input.value = value
|
||||||
|
L.DomEvent.on(input, 'blur', () => {
|
||||||
|
// Do not clear this.input when focus-blur
|
||||||
|
// empty input
|
||||||
|
if (input.value === value) return
|
||||||
|
this.input.value = input.value
|
||||||
|
this.sync()
|
||||||
|
})
|
||||||
|
return input
|
||||||
},
|
},
|
||||||
|
|
||||||
unselectAll: function (container) {
|
unselectAll: function (container) {
|
||||||
|
|
|
@ -1298,6 +1298,7 @@ a.add-datalayer:hover,
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
}
|
}
|
||||||
.umap-circle-icon {
|
.umap-circle-icon {
|
||||||
border: 1px solid white;
|
border: 1px solid white;
|
||||||
|
|
|
@ -475,6 +475,16 @@ describe('L.Util', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("#normalize()", function () {
|
||||||
|
|
||||||
|
if('should remove accents', function () {
|
||||||
|
// French é
|
||||||
|
assert.equal(L.Util.normalize('aéroport'), 'aeroport')
|
||||||
|
// American é
|
||||||
|
assert.equal(L.Util.normalize('aéroport'), 'aeroport')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("#sortFeatures()", function () {
|
describe("#sortFeatures()", function () {
|
||||||
let feat1, feat2, feat3
|
let feat1, feat2, feat3
|
||||||
before(function () {
|
before(function () {
|
||||||
|
|
|
@ -24,11 +24,8 @@ def pytest_configure(config):
|
||||||
settings.MEDIA_ROOT = TMP_ROOT
|
settings.MEDIA_ROOT = TMP_ROOT
|
||||||
|
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
|
||||||
shutil.rmtree(TMP_ROOT, ignore_errors=True)
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_teardown():
|
def pytest_runtest_teardown():
|
||||||
|
shutil.rmtree(TMP_ROOT, ignore_errors=True)
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
|
|
||||||
|
|
4
umap/tests/fixtures/circle.svg
vendored
Normal file
4
umap/tests/fixtures/circle.svg
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" id="circle" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
|
||||||
|
<path d="M14,7.5c0,3.5899-2.9101,6.5-6.5,6.5S1,11.0899,1,7.5S3.9101,1,7.5,1S14,3.9101,14,7.5z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 254 B |
4
umap/tests/fixtures/star.svg
vendored
Normal file
4
umap/tests/fixtures/star.svg
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" id="star" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
|
||||||
|
<path id="path4749-2-8-2" d="M7.5,0l-2,5h-5l4,3.5l-2,6l5-3.5
	l5,3.5l-2-6l4-3.5h-5L7.5,0z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 256 B |
217
umap/tests/integration/test_picto.py
Normal file
217
umap/tests/integration/test_picto.py
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
|
||||||
|
from umap.models import Map, Pictogram
|
||||||
|
|
||||||
|
from ..base import DataLayerFactory
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
DATALAYER_DATA = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"geometry": {
|
||||||
|
"type": "Point",
|
||||||
|
"coordinates": [13.68896484375, 48.55297816440071],
|
||||||
|
},
|
||||||
|
"properties": {"_umap_options": {"color": "DarkCyan"}, "name": "Here"},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_umap_options": {"displayOnLoad": True, "name": "FooBarFoo"},
|
||||||
|
}
|
||||||
|
FIXTURES = Path(__file__).parent.parent / "fixtures"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pictos():
|
||||||
|
path = FIXTURES / "star.svg"
|
||||||
|
Pictogram(name="star", pictogram=ContentFile(path.read_text(), path.name)).save()
|
||||||
|
path = FIXTURES / "circle.svg"
|
||||||
|
Pictogram(name="circle", pictogram=ContentFile(path.read_text(), path.name)).save()
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_change_picto_at_map_level(map, live_server, page, pictos):
|
||||||
|
# Faster than doing a login
|
||||||
|
map.edit_status = Map.ANONYMOUS
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||||
|
marker = page.locator(".umap-div-icon img")
|
||||||
|
expect(marker).to_have_count(1)
|
||||||
|
# Should have default img
|
||||||
|
expect(marker).to_have_attribute("src", "/static/umap/img/marker.png")
|
||||||
|
edit_settings = page.get_by_title("Edit map settings")
|
||||||
|
expect(edit_settings).to_be_visible()
|
||||||
|
edit_settings.click()
|
||||||
|
shape_settings = page.get_by_text("Default shape properties")
|
||||||
|
expect(shape_settings).to_be_visible()
|
||||||
|
shape_settings.click()
|
||||||
|
define = page.locator(".umap-field-iconUrl .define")
|
||||||
|
undefine = page.locator(".umap-field-iconUrl .undefine")
|
||||||
|
expect(define).to_be_visible()
|
||||||
|
expect(undefine).to_be_hidden()
|
||||||
|
define.click()
|
||||||
|
symbols = page.locator(".umap-pictogram-choice")
|
||||||
|
expect(symbols).to_have_count(2)
|
||||||
|
search = page.locator(".umap-pictogram-body input")
|
||||||
|
search.type("star")
|
||||||
|
expect(symbols).to_have_count(1)
|
||||||
|
symbols.click()
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/star.svg")
|
||||||
|
undefine.click()
|
||||||
|
expect(marker).to_have_attribute("src", "/static/umap/img/marker.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_change_picto_at_datalayer_level(map, live_server, page, pictos):
|
||||||
|
# Faster than doing a login
|
||||||
|
map.edit_status = Map.ANONYMOUS
|
||||||
|
map.settings["properties"]["iconUrl"] = "/uploads/pictogram/star.svg"
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||||
|
marker = page.locator(".umap-div-icon img")
|
||||||
|
expect(marker).to_have_count(1)
|
||||||
|
# Should have default img
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/star.svg")
|
||||||
|
# Edit datalayer
|
||||||
|
marker.click(modifiers=["Control", "Shift"])
|
||||||
|
settings = page.get_by_text("Layer properties")
|
||||||
|
expect(settings).to_be_visible()
|
||||||
|
shape_settings = page.get_by_text("Shape properties")
|
||||||
|
expect(shape_settings).to_be_visible()
|
||||||
|
shape_settings.click()
|
||||||
|
define = page.locator(".umap-field-iconUrl .define")
|
||||||
|
undefine = page.locator(".umap-field-iconUrl .undefine")
|
||||||
|
expect(define).to_be_visible()
|
||||||
|
expect(undefine).to_be_hidden()
|
||||||
|
define.click()
|
||||||
|
symbols = page.locator(".umap-pictogram-choice")
|
||||||
|
expect(symbols).to_have_count(2)
|
||||||
|
search = page.locator(".umap-pictogram-body input")
|
||||||
|
search.type("circle")
|
||||||
|
expect(symbols).to_have_count(1)
|
||||||
|
symbols.click()
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/circle.svg")
|
||||||
|
undefine.click()
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/star.svg")
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_change_picto_at_marker_level(map, live_server, page, pictos):
|
||||||
|
# Faster than doing a login
|
||||||
|
map.edit_status = Map.ANONYMOUS
|
||||||
|
map.settings["properties"]["iconUrl"] = "/uploads/pictogram/star.svg"
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||||
|
marker = page.locator(".umap-div-icon img")
|
||||||
|
expect(marker).to_have_count(1)
|
||||||
|
# Should have default img
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/star.svg")
|
||||||
|
# Edit marker
|
||||||
|
marker.click(modifiers=["Shift"])
|
||||||
|
settings = page.get_by_text("Feature properties")
|
||||||
|
expect(settings).to_be_visible()
|
||||||
|
shape_settings = page.get_by_text("Shape properties")
|
||||||
|
expect(shape_settings).to_be_visible()
|
||||||
|
shape_settings.click()
|
||||||
|
define = page.locator(".umap-field-iconUrl .define")
|
||||||
|
undefine = page.locator(".umap-field-iconUrl .undefine")
|
||||||
|
expect(define).to_be_visible()
|
||||||
|
expect(undefine).to_be_hidden()
|
||||||
|
define.click()
|
||||||
|
symbols = page.locator(".umap-pictogram-choice")
|
||||||
|
expect(symbols).to_have_count(2)
|
||||||
|
search = page.locator(".umap-pictogram-body input")
|
||||||
|
search.type("circle")
|
||||||
|
expect(symbols).to_have_count(1)
|
||||||
|
symbols.click()
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/circle.svg")
|
||||||
|
undefine.click()
|
||||||
|
expect(marker).to_have_attribute("src", "/uploads/pictogram/star.svg")
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_use_remote_url_as_picto(map, live_server, page, pictos):
|
||||||
|
# Faster than doing a login
|
||||||
|
map.edit_status = Map.ANONYMOUS
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||||
|
marker = page.locator(".umap-div-icon img")
|
||||||
|
expect(marker).to_have_count(1)
|
||||||
|
# Should have default img
|
||||||
|
expect(marker).to_have_attribute("src", "/static/umap/img/marker.png")
|
||||||
|
edit_settings = page.get_by_title("Edit map settings")
|
||||||
|
expect(edit_settings).to_be_visible()
|
||||||
|
edit_settings.click()
|
||||||
|
shape_settings = page.get_by_text("Default shape properties")
|
||||||
|
expect(shape_settings).to_be_visible()
|
||||||
|
shape_settings.click()
|
||||||
|
define = page.locator(".umap-field-iconUrl .define")
|
||||||
|
expect(define).to_be_visible()
|
||||||
|
define.click()
|
||||||
|
url_tab = page.get_by_role("button", name="URL")
|
||||||
|
input_el = page.get_by_placeholder("Add image URL")
|
||||||
|
expect(input_el).to_be_hidden()
|
||||||
|
expect(url_tab).to_be_visible()
|
||||||
|
url_tab.click()
|
||||||
|
expect(input_el).to_be_visible()
|
||||||
|
input_el.fill("https://foo.bar/img.jpg")
|
||||||
|
input_el.blur()
|
||||||
|
expect(marker).to_have_attribute("src", "https://foo.bar/img.jpg")
|
||||||
|
# Now close and reopen the form, it should still be the URL tab
|
||||||
|
close = page.locator("#umap-ui-container").get_by_title("Close")
|
||||||
|
expect(close).to_be_visible()
|
||||||
|
close.click()
|
||||||
|
edit_settings.click()
|
||||||
|
shape_settings.click()
|
||||||
|
modify = page.locator(".umap-field-iconUrl").get_by_text("Change")
|
||||||
|
expect(modify).to_be_visible()
|
||||||
|
modify.click()
|
||||||
|
# Should be on URL tab
|
||||||
|
expect(input_el).to_be_visible()
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_use_char_as_picto(map, live_server, page, pictos):
|
||||||
|
# Faster than doing a login
|
||||||
|
map.edit_status = Map.ANONYMOUS
|
||||||
|
map.save()
|
||||||
|
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{map.get_absolute_url()}?edit")
|
||||||
|
marker = page.locator(".umap-div-icon span")
|
||||||
|
# Should have default img, so not a span
|
||||||
|
expect(marker).to_have_count(0)
|
||||||
|
edit_settings = page.get_by_title("Edit map settings")
|
||||||
|
expect(edit_settings).to_be_visible()
|
||||||
|
edit_settings.click()
|
||||||
|
shape_settings = page.get_by_text("Default shape properties")
|
||||||
|
expect(shape_settings).to_be_visible()
|
||||||
|
shape_settings.click()
|
||||||
|
define = page.locator(".umap-field-iconUrl .define")
|
||||||
|
define.click()
|
||||||
|
url_tab = page.get_by_role("button", name="Emoji & Character")
|
||||||
|
input_el = page.get_by_placeholder("Type char or paste emoji")
|
||||||
|
expect(input_el).to_be_hidden()
|
||||||
|
expect(url_tab).to_be_visible()
|
||||||
|
url_tab.click()
|
||||||
|
expect(input_el).to_be_visible()
|
||||||
|
input_el.fill("♩")
|
||||||
|
input_el.blur()
|
||||||
|
expect(marker).to_have_count(1)
|
||||||
|
expect(marker).to_have_text("♩")
|
||||||
|
# Now close and reopen the form, it should still be the URL tab
|
||||||
|
close = page.locator("#umap-ui-container").get_by_title("Close")
|
||||||
|
expect(close).to_be_visible()
|
||||||
|
close.click()
|
||||||
|
edit_settings.click()
|
||||||
|
shape_settings.click()
|
||||||
|
preview = page.locator(".umap-pictogram-choice")
|
||||||
|
expect(preview).to_be_visible()
|
||||||
|
preview.click()
|
||||||
|
# Should be on URL tab
|
||||||
|
expect(input_el).to_be_visible()
|
Loading…
Reference in a new issue