quest control
This commit is contained in:
parent
7057919fd3
commit
2c871781a1
4 changed files with 429 additions and 363 deletions
|
@ -415,10 +415,9 @@ class QuestsFilter(BaseSchema):
|
||||||
response={200: list[QuestSchema], **auth_responses})
|
response={200: list[QuestSchema], **auth_responses})
|
||||||
@api_etag(permissions=True, quests=True)
|
@api_etag(permissions=True, quests=True)
|
||||||
def list_quests(request, filters: Query[QuestsFilter]):
|
def list_quests(request, filters: Query[QuestsFilter]):
|
||||||
quests = get_all_quests_for_request(request)
|
quest_types = filters.quest_type.split(',') if filters.quest_type else None
|
||||||
quest_types = frozenset(filters.quest_type.split(',')) if filters.quest_type else ()
|
quests = get_all_quests_for_request(request, quest_types)
|
||||||
if quest_types:
|
|
||||||
quests = [quest for quest in quests if quest.quest_type in quest_types]
|
|
||||||
if filters.level:
|
if filters.level:
|
||||||
quests = [quest for quest in quests if quest.level_id == filters.level]
|
quests = [quest for quest in quests if quest.level_id == filters.level]
|
||||||
return quests
|
return quests
|
||||||
|
|
|
@ -123,9 +123,16 @@ class QuestSchema(BaseSchema):
|
||||||
point: PointSchema
|
point: PointSchema
|
||||||
|
|
||||||
|
|
||||||
def get_all_quests_for_request(request) -> list[QuestSchema]:
|
def get_all_quests_for_request(request, requested_quest_types: Optional[list[str]]) -> list[QuestSchema]:
|
||||||
return list(chain(*(
|
if requested_quest_types is None:
|
||||||
quest.cached_get_all_for_request(request)
|
return list(chain(*(
|
||||||
for key, quest in quest_types.items()
|
quest.cached_get_all_for_request(request)
|
||||||
if request.user.is_superuser or key in request.user_permissions.quests
|
for key, quest in quest_types.items()
|
||||||
)))
|
if request.user.is_superuser or key in request.user_permissions.quests
|
||||||
|
)))
|
||||||
|
else:
|
||||||
|
return list(chain(*(
|
||||||
|
quest.cached_get_all_for_request(request)
|
||||||
|
for key, quest in quest_types.items()
|
||||||
|
if key in requested_quest_types and (request.user.is_superuser or key in request.user_permissions.quests)
|
||||||
|
)))
|
|
@ -1766,14 +1766,12 @@ blink {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-key {
|
.leaflet-control-key > .leaflet-control-expanding-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: 2rem 1fr;
|
||||||
|
|
||||||
> .leaflet-control-expanding-content {
|
> .key {
|
||||||
display: grid;
|
|
||||||
gap: 1rem;
|
|
||||||
grid-template-columns: 2rem 1fr;
|
|
||||||
|
|
||||||
> .key {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
grid-template-columns: subgrid;
|
grid-template-columns: subgrid;
|
||||||
|
@ -1786,63 +1784,76 @@ blink {
|
||||||
box-shadow: var(--color-foreground) 0 0 calc(var(--shadow-size) / 2), inset var(--color-foreground) 0 0 2px;
|
box-shadow: var(--color-foreground) 0 0 calc(var(--shadow-size) / 2), inset var(--color-foreground) 0 0 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&.leaflet-control-expanded > .leaflet-control-expanding-content {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-overlays {
|
.leaflet-control-overlays > .leaflet-control-expanding-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
> .leaflet-control-expanding-content {
|
.overlay-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
h4 {
|
||||||
|
|
||||||
.overlay-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
h4 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
font-family: 'Material Symbols Outlined';
|
|
||||||
content: 'arrow_right';
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
margin-left: 3ch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay-group.expanded h4::before {
|
|
||||||
content: 'arrow_drop_down';
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay-group:not(.expanded) label {
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
font-family: 'Material Symbols Outlined';
|
||||||
|
content: 'arrow_right';
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
cursor: pointer;
|
margin-left: 3ch;
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=checkbox] {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay-group.expanded h4::before {
|
||||||
|
content: 'arrow_drop_down';
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-group:not(.expanded) label {
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leaflet-control-quests > .leaflet-control-expanding-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=checkbox] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.leaflet-top.leaflet-right {
|
.leaflet-top.leaflet-right {
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1623,11 +1623,9 @@ c3nav = {
|
||||||
enabledIcon: c3nav._map_material_icon('grid_on'),
|
enabledIcon: c3nav._map_material_icon('grid_on'),
|
||||||
disabledIcon: c3nav._map_material_icon('grid_off'),
|
disabledIcon: c3nav._map_material_icon('grid_off'),
|
||||||
onEnable: () => {
|
onEnable: () => {
|
||||||
console.log('grid enable');
|
|
||||||
c3nav._gridLayer.addTo(c3nav.map);
|
c3nav._gridLayer.addTo(c3nav.map);
|
||||||
},
|
},
|
||||||
onDisable: () => {
|
onDisable: () => {
|
||||||
console.log('grid disable');
|
|
||||||
c3nav._gridLayer.remove();
|
c3nav._gridLayer.remove();
|
||||||
},
|
},
|
||||||
}).addTo(c3nav.map);
|
}).addTo(c3nav.map);
|
||||||
|
@ -1694,21 +1692,21 @@ c3nav = {
|
||||||
history.back(); // close the modal
|
history.back(); // close the modal
|
||||||
},
|
},
|
||||||
|
|
||||||
key_control: null,
|
legend_control: null,
|
||||||
create_key: function (theme_id) {
|
create_key: function (theme_id) {
|
||||||
c3nav_api.get(`map/legend/${theme_id}/`)
|
c3nav_api.get(`map/legend/${theme_id}/`)
|
||||||
.then(key => {
|
.then(key => {
|
||||||
const entries = [...key.base, ...key.groups, ...key.obstacles];
|
const entries = [...key.base, ...key.groups, ...key.obstacles];
|
||||||
const key_control = new KeyControl();
|
const legend_control = new LegendControl();
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
key_control.addKey(entry.title, entry.fill, entry.border);
|
legend_control.addKey(entry.title, entry.fill, entry.border);
|
||||||
}
|
}
|
||||||
if (c3nav.key_control !== null) {
|
if (c3nav.legend_control !== null) {
|
||||||
c3nav.map.removeControl(c3nav.key_control);
|
c3nav.map.removeControl(c3nav.legend_control);
|
||||||
}
|
}
|
||||||
if (entries.length > 0) {
|
if (entries.length > 0) {
|
||||||
c3nav.key_control = key_control;
|
c3nav.legend_control = legend_control;
|
||||||
key_control.addTo(c3nav.map);
|
legend_control.addTo(c3nav.map);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -2417,11 +2415,15 @@ ExpandingControl = L.Control.extend({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.render();
|
this.refresh();
|
||||||
|
|
||||||
return this._container;
|
return this._container;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refresh: function () {
|
||||||
|
this.render(this._content);
|
||||||
|
},
|
||||||
|
|
||||||
expand: function () {
|
expand: function () {
|
||||||
if (this._pinned) return;
|
if (this._pinned) return;
|
||||||
this._expanded = true;
|
this._expanded = true;
|
||||||
|
@ -2445,7 +2447,7 @@ ExpandingControl = L.Control.extend({
|
||||||
this.setStored('pinned', this._pinned);
|
this.setStored('pinned', this._pinned);
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function () {},
|
render: function (content) {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -2660,91 +2662,366 @@ ThemeControl = L.Control.extend({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
QuestsControl = L.Control.extend({
|
QuestsControl = ExpandingControl.extend({
|
||||||
options: {
|
options: {
|
||||||
position: 'topright',
|
position: 'topright',
|
||||||
addClasses: ''
|
addClasses: 'leaflet-control-quests',
|
||||||
|
icon: 'editor_choice',
|
||||||
|
storageKey: 'quests',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_questData: {},
|
||||||
|
|
||||||
onAdd: function () {
|
onAdd: function () {
|
||||||
this._container = L.DomUtil.create('div', 'leaflet-control-quests leaflet-bar ' + this.options.addClasses);
|
this._activeQuests = new Set(this.getStored('active', []));
|
||||||
this._button = L.DomUtil.create('a', 'material-symbols', this._container);
|
this._loadedQuests = new Set();
|
||||||
$(this._button).click(this.toggleQuests).dblclick(function (e) {
|
|
||||||
e.stopPropagation();
|
ExpandingControl.prototype.onAdd.call(this);
|
||||||
|
|
||||||
|
this.reloadQuests().catch(err => console.error(err));
|
||||||
|
|
||||||
|
$(this._container).on('change', 'input[type=checkbox]', e => {
|
||||||
|
const questName = e.target.dataset.quest;
|
||||||
|
if (e.target.checked) {
|
||||||
|
this.showQuest(questName);
|
||||||
|
} else {
|
||||||
|
this.hideQuest(questName);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this._button.innerText = c3nav._map_material_icon('editor_choice');
|
|
||||||
this._button.href = '#';
|
|
||||||
this._button.classList.toggle('control-disabled', false);
|
|
||||||
this.questsActive = false;
|
|
||||||
if (localStorageWrapper.getItem('showQuests')) {
|
|
||||||
this.showQuests();
|
|
||||||
}
|
|
||||||
return this._container;
|
return this._container;
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleQuests: function (e) {
|
render: function (container) {
|
||||||
if (e) e.preventDefault();
|
if (!container) return;
|
||||||
if (c3nav._questsControl.questsActive) {
|
const fragment = document.createDocumentFragment();
|
||||||
c3nav._questsControl.hideQuests();
|
const title = document.createElement('h4');
|
||||||
} else {
|
title.textContent = 'Quests';
|
||||||
c3nav._questsControl.showQuests();
|
|
||||||
|
fragment.append(title);
|
||||||
|
|
||||||
|
for (const quest_name in c3nav.user_data.quests) {
|
||||||
|
const quest = c3nav.user_data.quests[quest_name];
|
||||||
|
|
||||||
|
const label = document.createElement('label');
|
||||||
|
const checkbox = document.createElement('input');
|
||||||
|
checkbox.type = 'checkbox';
|
||||||
|
checkbox.dataset.quest = quest_name;
|
||||||
|
|
||||||
|
if (this._activeQuests.has(quest_name)) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
|
label.append(checkbox, quest.label);
|
||||||
|
|
||||||
|
fragment.append(label);
|
||||||
}
|
}
|
||||||
|
container.replaceChildren(...fragment.children);
|
||||||
},
|
},
|
||||||
|
|
||||||
showQuests: function () {
|
showQuest: function (name) {
|
||||||
if (this.questsActive) return;
|
if (this._activeQuests.has(name)) return;
|
||||||
this._button.innerText = c3nav._map_material_icon('editor_choice');
|
this._activeQuests.add(name);
|
||||||
this._button.classList.toggle('control-disabled', false);
|
this.setStored('active', [...this._activeQuests]);
|
||||||
this.questsActive = true;
|
this.reloadQuests().catch(err => console.error(err));
|
||||||
localStorageWrapper.setItem('showQuests', '1');
|
|
||||||
this.reloadQuests();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
reloadQuests: function() {
|
hideQuest: function (name) {
|
||||||
if (!this.questsActive) return;
|
if (!this._activeQuests.has(name)) return;
|
||||||
c3nav_api.get('map/quests/')
|
this._activeQuests.delete(name);
|
||||||
.then((data) => {
|
this.setStored('active', [...this._activeQuests]);
|
||||||
for (const level_id in c3nav._questsLayers) {
|
this.reloadQuests().catch(err => console.error(err));
|
||||||
c3nav._questsLayers[level_id].clearLayers();
|
|
||||||
}
|
|
||||||
for (const quest of data) {
|
|
||||||
const quest_icon = c3nav._map_material_icon(c3nav.user_data.quests[quest.quest_type].icon ?? 'editor_choice');
|
|
||||||
L.geoJson(quest.point, {
|
|
||||||
pointToLayer: (geom, latlng) => {
|
|
||||||
return L.marker(latlng, {
|
|
||||||
icon: L.divIcon({
|
|
||||||
className: 'quest-icon',
|
|
||||||
html: `<span>${quest_icon}</span>`,
|
|
||||||
iconSize: [24, 24],
|
|
||||||
iconAnchor: [12, 12],
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.addTo(c3nav._questsLayers[quest.level_id])
|
|
||||||
.on('click', function() {
|
|
||||||
c3nav.open_modal();
|
|
||||||
$.get(`/editor/quests/${quest.quest_type}/${quest.identifier}`, c3nav._modal_loaded).fail(c3nav._modal_error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hideQuests: function () {
|
reloadQuests: async function() {
|
||||||
if (!this.questsActive) return;
|
console.log(this);
|
||||||
|
const activeQuests = this._activeQuests;
|
||||||
|
const removed = this._loadedQuests.difference(activeQuests);
|
||||||
|
const added = activeQuests.difference(this._loadedQuests);
|
||||||
|
|
||||||
|
if (removed.size === 0 && added.size === 0) return;
|
||||||
|
|
||||||
|
const questData = this._questData;
|
||||||
|
|
||||||
|
if (added.size > 0) {
|
||||||
|
for(const name of added) {
|
||||||
|
questData[name] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const added_param = [...added].join(',');
|
||||||
|
const data = await c3nav_api.get(`map/quests/?quest_type=${added_param}`);
|
||||||
|
for (const quest of data) {
|
||||||
|
questData[quest.quest_type].push(quest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name of removed) {
|
||||||
|
delete questData[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._questData = questData;
|
||||||
|
this._loadedQuests = new Set([...activeQuests]);
|
||||||
|
|
||||||
for (const level_id in c3nav._questsLayers) {
|
for (const level_id in c3nav._questsLayers) {
|
||||||
c3nav._questsLayers[level_id].clearLayers();
|
c3nav._questsLayers[level_id].clearLayers();
|
||||||
}
|
}
|
||||||
this._button.innerText = c3nav._map_material_icon('editor_choice');
|
|
||||||
this._button.classList.toggle('control-disabled', true);
|
for (const quest_type in this._questData) {
|
||||||
this.questsActive = false;
|
const quests = this._questData[quest_type];
|
||||||
localStorageWrapper.removeItem('showQuests');
|
const quest_icon = c3nav._map_material_icon(c3nav.user_data.quests[quest_type].icon ?? 'editor_choice');
|
||||||
}
|
|
||||||
|
for (const quest of quests) {
|
||||||
|
L.geoJson(quest.point, {
|
||||||
|
pointToLayer: (geom, latlng) => {
|
||||||
|
return L.marker(latlng, {
|
||||||
|
icon: L.divIcon({
|
||||||
|
className: 'quest-icon',
|
||||||
|
html: `<span>${quest_icon}</span>`,
|
||||||
|
iconSize: [24, 24],
|
||||||
|
iconAnchor: [12, 12],
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(c3nav._questsLayers[quest.level_id])
|
||||||
|
.on('click', function () {
|
||||||
|
c3nav.open_modal();
|
||||||
|
$.get(`/editor/quests/${quest_type}/${quest.identifier}`, c3nav._modal_loaded).fail(c3nav._modal_error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
LegendControl = ExpandingControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topright',
|
||||||
|
addClasses: 'leaflet-control-key',
|
||||||
|
icon: 'legend_toggle',
|
||||||
|
storageKey: 'legend',
|
||||||
|
},
|
||||||
|
_keys: [],
|
||||||
|
|
||||||
|
addKey: function (name, background, border) {
|
||||||
|
this._keys.push({
|
||||||
|
name,
|
||||||
|
background,
|
||||||
|
border,
|
||||||
|
});
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function (container) {
|
||||||
|
if (!container) return;
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
for (const key of this._keys) {
|
||||||
|
const key_container = document.createElement('div');
|
||||||
|
key_container.classList.add('key');
|
||||||
|
const color = document.createElement('div');
|
||||||
|
color.classList.add('key-color');
|
||||||
|
if (key.background !== null) {
|
||||||
|
color.style.backgroundColor = key.background;
|
||||||
|
}
|
||||||
|
if (key.border !== null) {
|
||||||
|
color.style.borderColor = key.border;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = document.createElement('div');
|
||||||
|
name.innerText = key.name;
|
||||||
|
key_container.append(color, name);
|
||||||
|
fragment.append(key_container);
|
||||||
|
}
|
||||||
|
container.replaceChildren(...fragment.children);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
OverlayControl = ExpandingControl.extend({
|
||||||
|
options: {
|
||||||
|
position: 'topright',
|
||||||
|
addClasses: 'leaflet-control-overlays',
|
||||||
|
icon: 'stacks',
|
||||||
|
storageKey: 'overlays',
|
||||||
|
levels: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
_overlays: {},
|
||||||
|
_ungrouped: [],
|
||||||
|
_groups: {},
|
||||||
|
|
||||||
|
initialize: function ({levels, ...config}) {
|
||||||
|
this.config = config;
|
||||||
|
this._levels = levels;
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd: function () {
|
||||||
|
|
||||||
|
const initialActiveOverlays = this.getStored('active', []);
|
||||||
|
const initialCollapsedGroups = this.getStored('collapsed', []);
|
||||||
|
|
||||||
|
for (const overlay of initialActiveOverlays) {
|
||||||
|
if (overlay in this._overlays) {
|
||||||
|
this._overlays[overlay].visible = true;
|
||||||
|
this._overlays[overlay].enable(this._levels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const group of initialCollapsedGroups) {
|
||||||
|
if (group in this._groups) {
|
||||||
|
this._groups[group].expanded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpandingControl.prototype.onAdd.call(this);
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
|
||||||
|
$(this._container).on('change', 'input[type=checkbox]', e => {
|
||||||
|
this._overlays[e.target.dataset.id].visible = e.target.checked;
|
||||||
|
this.updateOverlay(e.target.dataset.id);
|
||||||
|
});
|
||||||
|
$(this._container).on('click', '.content h4', e => {
|
||||||
|
this.toggleGroup(e.target.parentElement.dataset.group);
|
||||||
|
});
|
||||||
|
return this._container;
|
||||||
|
},
|
||||||
|
|
||||||
|
addOverlay: function (overlay) {
|
||||||
|
this._overlays[overlay.id] = overlay;
|
||||||
|
if (overlay.group == null) {
|
||||||
|
this._ungrouped.push(overlay);
|
||||||
|
} else {
|
||||||
|
if (overlay.group in this._groups) {
|
||||||
|
this._groups[overlay.group].overlays.push(overlay);
|
||||||
|
} else {
|
||||||
|
this._groups[overlay.group] = {
|
||||||
|
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group),
|
||||||
|
overlays: [overlay],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateOverlay: function (id) {
|
||||||
|
const overlay = this._overlays[id];
|
||||||
|
if (overlay.visible) {
|
||||||
|
overlay.enable(this._levels);
|
||||||
|
} else {
|
||||||
|
overlay.disable(this._levels);
|
||||||
|
}
|
||||||
|
const activeOverlays = Object.keys(this._overlays).filter(k => this._overlays[k].visible);
|
||||||
|
this.setStored('active', activeOverlays);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function (container) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const ungrouped = document.createDocumentFragment();
|
||||||
|
const groups = document.createDocumentFragment();
|
||||||
|
|
||||||
|
const render_overlays = (overlays, container) => {
|
||||||
|
for (const overlay of overlays) {
|
||||||
|
const label = document.createElement('label');
|
||||||
|
const checkbox = document.createElement('input');
|
||||||
|
checkbox.type = 'checkbox';
|
||||||
|
checkbox.dataset.id = overlay.id;
|
||||||
|
if (overlay.visible) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
}
|
||||||
|
label.append(checkbox, overlay.title);
|
||||||
|
container.append(label);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render_overlays(this._ungrouped, ungrouped);
|
||||||
|
|
||||||
|
for (const group in this._groups) {
|
||||||
|
const group_container = document.createElement('div');
|
||||||
|
group_container.classList.add('overlay-group');
|
||||||
|
if (this._groups[group].expanded) {
|
||||||
|
group_container.classList.add('expanded');
|
||||||
|
}
|
||||||
|
this._groups[group].el = group_container;
|
||||||
|
group_container.dataset.group = group;
|
||||||
|
const title = document.createElement('h4');
|
||||||
|
title.innerText = group;
|
||||||
|
group_container.append(title);
|
||||||
|
render_overlays(this._groups[group].overlays, group_container);
|
||||||
|
groups.append(group_container);
|
||||||
|
}
|
||||||
|
container.replaceChildren(...ungrouped.children, ...groups.children);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleGroup: function (name) {
|
||||||
|
const group = this._groups[name];
|
||||||
|
group.expanded = !group.expanded;
|
||||||
|
group.el.classList.toggle('expanded', group.expanded);
|
||||||
|
const collapsedGroups = Object.keys(this._groups).filter(k => !this._groups[k].expanded);
|
||||||
|
this.setStored('collapsed', collapsedGroups);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
var SvgIcon = L.Icon.extend({
|
||||||
|
options: {
|
||||||
|
// @section
|
||||||
|
// @aka DivIcon options
|
||||||
|
iconSize: [12, 12], // also can be set through CSS
|
||||||
|
|
||||||
|
// iconAnchor: (Point),
|
||||||
|
// popupAnchor: (Point),
|
||||||
|
|
||||||
|
// @option html: String|SVGElement = ''
|
||||||
|
// Custom HTML code to put inside the div element, empty by default. Alternatively,
|
||||||
|
// an instance of `SVGElement`.
|
||||||
|
iconSvg: null,
|
||||||
|
shadowSvg: null,
|
||||||
|
|
||||||
|
// @option bgPos: Point = [0, 0]
|
||||||
|
// Optional relative position of the background, in pixels
|
||||||
|
bgPos: null,
|
||||||
|
|
||||||
|
className: 'leaflet-svg-icon'
|
||||||
|
},
|
||||||
|
|
||||||
|
// @method createIcon(oldIcon?: HTMLElement): HTMLElement
|
||||||
|
// Called internally when the icon has to be shown, returns a `<img>` HTML element
|
||||||
|
// styled according to the options.
|
||||||
|
createIcon: function (oldIcon) {
|
||||||
|
return this._createIcon('icon', oldIcon);
|
||||||
|
},
|
||||||
|
|
||||||
|
// @method createShadow(oldIcon?: HTMLElement): HTMLElement
|
||||||
|
// As `createIcon`, but for the shadow beneath it.
|
||||||
|
createShadow: function (oldIcon) {
|
||||||
|
return this._createIcon('shadow', oldIcon);
|
||||||
|
},
|
||||||
|
|
||||||
|
_createIcon: function (name, oldIcon) {
|
||||||
|
const src = this.options[`${name}Svg`];
|
||||||
|
|
||||||
|
if (!src) {
|
||||||
|
if (name === 'icon') {
|
||||||
|
throw new Error('iconSvg not set in Icon options (see the docs).');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let svgEl;
|
||||||
|
if (src instanceof SVGElement) {
|
||||||
|
svgEl = src;
|
||||||
|
} else {
|
||||||
|
svgEl = (new DOMParser()).parseFromString(src, 'image/svg+xml').documentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setIconStyles(svgEl, name);
|
||||||
|
|
||||||
|
return svgEl;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
L.SquareGridLayer = L.Layer.extend({
|
L.SquareGridLayer = L.Layer.extend({
|
||||||
initialize: function (config) {
|
initialize: function (config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
@ -2844,234 +3121,6 @@ L.SquareGridLayer = L.Layer.extend({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
KeyControl = ExpandingControl.extend({
|
|
||||||
options: {
|
|
||||||
position: 'topright',
|
|
||||||
addClasses: 'leaflet-control-key',
|
|
||||||
icon: 'legend_toggle',
|
|
||||||
storageKey: 'key',
|
|
||||||
},
|
|
||||||
_keys: [],
|
|
||||||
|
|
||||||
addKey: function (name, background, border) {
|
|
||||||
this._keys.push({
|
|
||||||
name,
|
|
||||||
background,
|
|
||||||
border,
|
|
||||||
});
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function () {
|
|
||||||
if (!this._content) return;
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
for (const key of this._keys) {
|
|
||||||
const key_container = document.createElement('div');
|
|
||||||
key_container.classList.add('key');
|
|
||||||
const color = document.createElement('div');
|
|
||||||
color.classList.add('key-color');
|
|
||||||
if (key.background !== null) {
|
|
||||||
color.style.backgroundColor = key.background;
|
|
||||||
}
|
|
||||||
if (key.border !== null) {
|
|
||||||
color.style.borderColor = key.border;
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = document.createElement('div');
|
|
||||||
name.innerText = key.name;
|
|
||||||
key_container.append(color, name);
|
|
||||||
fragment.append(key_container);
|
|
||||||
}
|
|
||||||
this._content.replaceChildren(...fragment.children);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
OverlayControl = ExpandingControl.extend({
|
|
||||||
options: {
|
|
||||||
position: 'topright',
|
|
||||||
addClasses: 'leaflet-control-overlays',
|
|
||||||
icon: 'stacks',
|
|
||||||
storageKey: 'overlays',
|
|
||||||
levels: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
_overlays: {},
|
|
||||||
_ungrouped: [],
|
|
||||||
_groups: {},
|
|
||||||
|
|
||||||
initialize: function ({levels, ...config}) {
|
|
||||||
this.config = config;
|
|
||||||
this._levels = levels;
|
|
||||||
},
|
|
||||||
|
|
||||||
onAdd: function () {
|
|
||||||
|
|
||||||
const initialActiveOverlays = this.getStored('active', []);
|
|
||||||
const initialCollapsedGroups = this.getStored('collapsed', []);
|
|
||||||
|
|
||||||
for (const overlay of initialActiveOverlays) {
|
|
||||||
if (overlay in this._overlays) {
|
|
||||||
this._overlays[overlay].visible = true;
|
|
||||||
this._overlays[overlay].enable(this._levels);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const group of initialCollapsedGroups) {
|
|
||||||
if (group in this._groups) {
|
|
||||||
this._groups[group].expanded = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ExpandingControl.prototype.onAdd.call(this);
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
|
|
||||||
$(this._container).on('change', 'input[type=checkbox]', e => {
|
|
||||||
this._overlays[e.target.dataset.id].visible = e.target.checked;
|
|
||||||
this.updateOverlay(e.target.dataset.id);
|
|
||||||
});
|
|
||||||
$(this._container).on('click', '.content h4', e => {
|
|
||||||
this.toggleGroup(e.target.parentElement.dataset.group);
|
|
||||||
});
|
|
||||||
return this._container;
|
|
||||||
},
|
|
||||||
|
|
||||||
addOverlay: function (overlay) {
|
|
||||||
this._overlays[overlay.id] = overlay;
|
|
||||||
if (overlay.group == null) {
|
|
||||||
this._ungrouped.push(overlay);
|
|
||||||
} else {
|
|
||||||
if (overlay.group in this._groups) {
|
|
||||||
this._groups[overlay.group].overlays.push(overlay);
|
|
||||||
} else {
|
|
||||||
this._groups[overlay.group] = {
|
|
||||||
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(overlay.group),
|
|
||||||
overlays: [overlay],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateOverlay: function (id) {
|
|
||||||
const overlay = this._overlays[id];
|
|
||||||
if (overlay.visible) {
|
|
||||||
overlay.enable(this._levels);
|
|
||||||
} else {
|
|
||||||
overlay.disable(this._levels);
|
|
||||||
}
|
|
||||||
const activeOverlays = Object.keys(this._overlays).filter(k => this._overlays[k].visible);
|
|
||||||
this.setStored('active', activeOverlays);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function () {
|
|
||||||
if (!this._content) return;
|
|
||||||
|
|
||||||
const ungrouped = document.createDocumentFragment();
|
|
||||||
const groups = document.createDocumentFragment();
|
|
||||||
|
|
||||||
|
|
||||||
const render_overlays = (overlays, container) => {
|
|
||||||
for (const overlay of overlays) {
|
|
||||||
const label = document.createElement('label');
|
|
||||||
const checkbox = document.createElement('input');
|
|
||||||
checkbox.type = 'checkbox';
|
|
||||||
checkbox.dataset.id = overlay.id;
|
|
||||||
if (overlay.visible) {
|
|
||||||
checkbox.checked = true;
|
|
||||||
}
|
|
||||||
label.append(checkbox, overlay.title);
|
|
||||||
container.append(label);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render_overlays(this._ungrouped, ungrouped);
|
|
||||||
|
|
||||||
|
|
||||||
for (const group in this._groups) {
|
|
||||||
const group_container = document.createElement('div');
|
|
||||||
group_container.classList.add('overlay-group');
|
|
||||||
if (this._groups[group].expanded) {
|
|
||||||
group_container.classList.add('expanded');
|
|
||||||
}
|
|
||||||
this._groups[group].el = group_container;
|
|
||||||
group_container.dataset.group = group;
|
|
||||||
const title = document.createElement('h4');
|
|
||||||
title.innerText = group;
|
|
||||||
group_container.append(title);
|
|
||||||
render_overlays(this._groups[group].overlays, group_container);
|
|
||||||
groups.append(group_container);
|
|
||||||
}
|
|
||||||
this._content.replaceChildren(...ungrouped.children, ...groups.children);
|
|
||||||
},
|
|
||||||
|
|
||||||
toggleGroup: function (name) {
|
|
||||||
const group = this._groups[name];
|
|
||||||
group.expanded = !group.expanded;
|
|
||||||
group.el.classList.toggle('expanded', group.expanded);
|
|
||||||
const collapsedGroups = Object.keys(this._groups).filter(k => !this._groups[k].expanded);
|
|
||||||
this.setStored('collapsed', collapsedGroups);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
var SvgIcon = L.Icon.extend({
|
|
||||||
options: {
|
|
||||||
// @section
|
|
||||||
// @aka DivIcon options
|
|
||||||
iconSize: [12, 12], // also can be set through CSS
|
|
||||||
|
|
||||||
// iconAnchor: (Point),
|
|
||||||
// popupAnchor: (Point),
|
|
||||||
|
|
||||||
// @option html: String|SVGElement = ''
|
|
||||||
// Custom HTML code to put inside the div element, empty by default. Alternatively,
|
|
||||||
// an instance of `SVGElement`.
|
|
||||||
iconSvg: null,
|
|
||||||
shadowSvg: null,
|
|
||||||
|
|
||||||
// @option bgPos: Point = [0, 0]
|
|
||||||
// Optional relative position of the background, in pixels
|
|
||||||
bgPos: null,
|
|
||||||
|
|
||||||
className: 'leaflet-svg-icon'
|
|
||||||
},
|
|
||||||
|
|
||||||
// @method createIcon(oldIcon?: HTMLElement): HTMLElement
|
|
||||||
// Called internally when the icon has to be shown, returns a `<img>` HTML element
|
|
||||||
// styled according to the options.
|
|
||||||
createIcon: function (oldIcon) {
|
|
||||||
return this._createIcon('icon', oldIcon);
|
|
||||||
},
|
|
||||||
|
|
||||||
// @method createShadow(oldIcon?: HTMLElement): HTMLElement
|
|
||||||
// As `createIcon`, but for the shadow beneath it.
|
|
||||||
createShadow: function (oldIcon) {
|
|
||||||
return this._createIcon('shadow', oldIcon);
|
|
||||||
},
|
|
||||||
|
|
||||||
_createIcon: function (name, oldIcon) {
|
|
||||||
const src = this.options[`${name}Svg`];
|
|
||||||
|
|
||||||
if (!src) {
|
|
||||||
if (name === 'icon') {
|
|
||||||
throw new Error('iconSvg not set in Icon options (see the docs).');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let svgEl;
|
|
||||||
if (src instanceof SVGElement) {
|
|
||||||
svgEl = src;
|
|
||||||
} else {
|
|
||||||
svgEl = (new DOMParser()).parseFromString(src, 'image/svg+xml').documentElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._setIconStyles(svgEl, name);
|
|
||||||
|
|
||||||
return svgEl;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
class DataOverlay {
|
class DataOverlay {
|
||||||
levels = null;
|
levels = null;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue