better overlay control for the editor
This commit is contained in:
parent
50aef0bed3
commit
afaf6e0085
2 changed files with 284 additions and 38 deletions
|
@ -469,3 +469,90 @@ label.theme-color-label {
|
|||
.theme-color-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaflet-control-overlays {
|
||||
background-color: white;
|
||||
border-radius: 0.5rem;
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
|
||||
&.leaflet-control-overlays-expanded > .collapsed-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .pin-toggle {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
color: black;
|
||||
font-size: 1.5rem;
|
||||
top: 0.5rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border: 1px solid #DCDCDC;
|
||||
border-radius: 0.5rem;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 0 2px black;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: black;
|
||||
background: #DCDCDC;
|
||||
box-shadow: inset 0 0 2px black;
|
||||
}
|
||||
}
|
||||
|
||||
&.leaflet-control-overlays-expanded > .pin-toggle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> .content {
|
||||
display: none;
|
||||
max-height: calc(100vh - 160px);
|
||||
overflow: scroll;
|
||||
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
.overlay-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '▹ ';
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
margin-left: 3ch;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay-group.expanded h4::before {
|
||||
content: '▿ ';
|
||||
}
|
||||
|
||||
.overlay-group:not(.expanded) label {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.leaflet-control-overlays-expanded > .content {
|
||||
display: flex;
|
||||
}
|
||||
}
|
|
@ -89,13 +89,14 @@ editor = {
|
|||
},
|
||||
|
||||
// sources
|
||||
sources: {},
|
||||
get_sources: function () {
|
||||
// load sources
|
||||
editor._sources_control = L.control.layers([], [], { autoZIndex: false });
|
||||
|
||||
c3nav_api.get('mapdata/sources')
|
||||
.then(async sources => {
|
||||
.then(sources => Object.groupBy(sources, s => s.group ?? 'Ungrouped'))
|
||||
.then(async sourceGroups => {
|
||||
const control = new OverlayControl();
|
||||
for (const key in sourceGroups) {
|
||||
const sources = sourceGroups[key];
|
||||
for (var i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
editor.sources[source.id] = source;
|
||||
|
@ -103,13 +104,13 @@ editor = {
|
|||
options = {opacity: 0.3};
|
||||
source.layer = L.imageOverlay('/editor/sourceimage/' + source.name, bounds, options);
|
||||
const is_svg = source.name.endsWith('.svg');
|
||||
editor._sources_control.addOverlay(source.layer, is_svg ? `${source.name} (image overlay)` : source.name);
|
||||
control.addOverlay(source.layer, is_svg ? `${source.name} (image overlay)` : source.name, key);
|
||||
|
||||
if (is_svg) {
|
||||
source.svg_el = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
source.svg_el.setAttribute('xmlns', "http://www.w3.org/2000/svg");
|
||||
source.svg_layer = L.svgOverlay(source.svg_el, bounds, options);
|
||||
editor._sources_control.addOverlay(source.svg_layer, `${source.name} (svg overlay)`);
|
||||
control.addOverlay(source.svg_layer, `${source.name} (svg overlay)`, key);
|
||||
source.svg_layer.on('add', function () {
|
||||
if (source.svg_promise) return;
|
||||
source.svg_promise = fetch(`/editor/sourceimage/${source.name}`)
|
||||
|
@ -131,8 +132,9 @@ editor = {
|
|||
|
||||
|
||||
}
|
||||
if (sources.length) editor._sources_control.addTo(editor.map);
|
||||
})
|
||||
}
|
||||
control.addTo(editor.map);
|
||||
});
|
||||
},
|
||||
|
||||
// sidebar
|
||||
|
@ -1591,6 +1593,163 @@ LevelControl = L.Control.extend({
|
|||
}
|
||||
});
|
||||
|
||||
OverlayControl = L.Control.extend({
|
||||
options: {position: 'topright', addClasses: ''},
|
||||
_overlays: {},
|
||||
_groups: {},
|
||||
_initialActiveOverlays: null,
|
||||
_initialCollapsedGroups: null,
|
||||
|
||||
onAdd: function () {
|
||||
this._initialActiveOverlays = JSON.parse(localStorage.getItem('c3nav.editor.overlays.active-overlays') ?? '[]');
|
||||
this._initialCollapsedGroups = JSON.parse(localStorage.getItem('c3nav.editor.overlays.collapsedGroups') ?? '[]');
|
||||
const pinned = JSON.parse(localStorage.getItem('c3nav.editor.overlays.pinned') ?? 'false');
|
||||
|
||||
this._container = L.DomUtil.create('div', 'leaflet-control-overlays ' + this.options.addClasses);
|
||||
this._container.classList.toggle('leaflet-control-overlays-expanded', pinned);
|
||||
this._content = L.DomUtil.create('div', 'content');
|
||||
const collapsed = L.DomUtil.create('div', 'collapsed-toggle leaflet-control-layers-toggle');
|
||||
this._pin = L.DomUtil.create('div', 'pin-toggle');
|
||||
this._pin.classList.toggle('active', pinned);
|
||||
this._pin.innerText = '🖈';
|
||||
this._container.append(this._pin, this._content, collapsed);
|
||||
this._expanded = pinned;
|
||||
this._pinned = pinned;
|
||||
|
||||
if (!L.Browser.android) {
|
||||
L.DomEvent.on(this._container, {
|
||||
mouseenter: this.expand,
|
||||
mouseleave: this.collapse
|
||||
}, this);
|
||||
}
|
||||
|
||||
if (!L.Browser.touch) {
|
||||
L.DomEvent.on(this._container, 'focus', this.expand, this);
|
||||
L.DomEvent.on(this._container, 'blur', this.collapse, this);
|
||||
}
|
||||
|
||||
for (const overlay of this._initialActiveOverlays) {
|
||||
if (overlay in this._overlays) {
|
||||
this._overlays[overlay].visible = true;
|
||||
this._overlays[overlay].layer.addTo(this._map);
|
||||
}
|
||||
}
|
||||
|
||||
for (const group of this._initialCollapsedGroups) {
|
||||
if (group in this._groups) {
|
||||
this._groups[group].expanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.render();
|
||||
|
||||
$(this._container).on('change', 'input[type=checkbox]', e => {
|
||||
this._overlays[e.target.dataset.source].visible = e.target.checked;
|
||||
this.updateOverlay(e.target.dataset.source);
|
||||
});
|
||||
$(this._container).on('click', 'div.pin-toggle', e => {
|
||||
this.togglePinned();
|
||||
});
|
||||
$(this._container).on('click', '.content h4', e => {
|
||||
this.toggleGroup(e.target.parentElement.dataset.group);
|
||||
});
|
||||
$(this._container).on('mousedown pointerdown wheel', e => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
return this._container;
|
||||
},
|
||||
|
||||
addOverlay: function (layer, name, group) {
|
||||
const l = {
|
||||
layer,
|
||||
name,
|
||||
group,
|
||||
visible: this._initialActiveOverlays !== null && this._initialActiveOverlays.includes(name),
|
||||
};
|
||||
this._overlays[name] = l;
|
||||
if (group in this._groups) {
|
||||
this._groups[group].overlays.push(l);
|
||||
} else {
|
||||
this._groups[group] = {
|
||||
expanded: this._initialCollapsedGroups === null || !this._initialCollapsedGroups.includes(group),
|
||||
overlays: [l],
|
||||
};
|
||||
}
|
||||
this.render();
|
||||
},
|
||||
|
||||
updateOverlay: function(id) {
|
||||
const overlay = this._overlays[id];
|
||||
if (overlay.visible) {
|
||||
overlay.layer.addTo(this._map);
|
||||
} else {
|
||||
this._map.removeLayer(overlay.layer);
|
||||
}
|
||||
const activeOverlays = Object.keys(this._overlays).filter(k => this._overlays[k].visible);
|
||||
localStorage.setItem('c3nav.editor.overlays.active-overlays', JSON.stringify(activeOverlays));
|
||||
},
|
||||
|
||||
render: function () {
|
||||
if (!this._content) return;
|
||||
const groups = document.createDocumentFragment();
|
||||
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);
|
||||
for (const overlay of this._groups[group].overlays) {
|
||||
const label = document.createElement('label');
|
||||
const checkbox = document.createElement('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.dataset.source = overlay.name;
|
||||
if (overlay.visible) {
|
||||
checkbox.checked = true;
|
||||
}
|
||||
label.append(checkbox, overlay.name);
|
||||
group_container.append(label);
|
||||
}
|
||||
groups.append(group_container);
|
||||
}
|
||||
this._content.replaceChildren(...groups.children);
|
||||
},
|
||||
|
||||
expand: function () {
|
||||
if (this._pinned) return;
|
||||
this._expanded = true;
|
||||
this._container.classList.add('leaflet-control-overlays-expanded');
|
||||
return this;
|
||||
},
|
||||
|
||||
collapse: function () {
|
||||
if (this._pinned) return;
|
||||
this._expanded = false;
|
||||
this._container.classList.remove('leaflet-control-overlays-expanded');
|
||||
return this;
|
||||
},
|
||||
|
||||
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);
|
||||
localStorage.setItem('c3nav.editor.overlays.collapsed-groups', JSON.stringify(collapsedGroups));
|
||||
},
|
||||
|
||||
togglePinned: function() {
|
||||
this._pinned = !this._pinned;
|
||||
if (this._pinned) {
|
||||
this._expanded = true;
|
||||
}
|
||||
this._pin.classList.toggle('active', this._pinned);
|
||||
localStorage.setItem('c3nav.editor.overlays.pinned', JSON.stringify(this._pinned));
|
||||
},
|
||||
});
|
||||
|
||||
if ($('#sidebar').length) {
|
||||
editor.init();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue