Skip to content

Commit fe00b4e

Browse files
committed
fix: improved demo html
1 parent 130ac5c commit fe00b4e

File tree

8 files changed

+3637
-32
lines changed

8 files changed

+3637
-32
lines changed

demo/demo.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,32 @@ h3 {
4141
padding: 30px;
4242
max-width: 1000px;
4343
margin: auto;
44+
/* Make room for DevPanel on the right */
45+
margin-right: 380px;
46+
transition: margin-right 0.2s ease;
47+
}
48+
49+
/* When DevPanel is collapsed, use more space */
50+
body.devpanel-collapsed .wrapper {
51+
margin-right: 60px;
4452
}
4553

4654
.map {
4755
height: 500px;
4856
}
57+
58+
/* Responsive adjustments for DevPanel */
59+
@media (max-width: 1200px) {
60+
.wrapper {
61+
margin-right: auto;
62+
max-width: none;
63+
padding-right: 400px;
64+
}
65+
}
66+
67+
@media (max-width: 768px) {
68+
.wrapper {
69+
padding-right: 30px;
70+
margin-bottom: 50vh;
71+
}
72+
}

demo/devpanel/DevPanel.js

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
/**
2+
* DevPanel - A developer debugging panel for Leaflet-Geoman
3+
*
4+
* Provides a collapsible right sidebar with modules for:
5+
* - GeoJSON import/export
6+
* - State inspection
7+
* - Event logging
8+
* - Layer inspection
9+
*/
10+
11+
class DevPanel {
12+
constructor(map, options = {}) {
13+
this.map = map;
14+
this.options = {
15+
position: 'right',
16+
width: 360,
17+
collapsed: false,
18+
persistState: true,
19+
storageKey: 'geoman-devpanel',
20+
...options,
21+
};
22+
23+
this.modules = new Map();
24+
this.isCollapsed = this.options.collapsed;
25+
26+
this._loadState();
27+
this._createContainer();
28+
this._setupKeyboardShortcuts();
29+
}
30+
31+
/**
32+
* Create the main panel container
33+
*/
34+
_createContainer() {
35+
// Main panel container
36+
this.container = document.createElement('div');
37+
this.container.className = 'devpanel';
38+
this.container.style.width = this.isCollapsed
39+
? '40px'
40+
: `${this.options.width}px`;
41+
42+
if (this.isCollapsed) {
43+
this.container.classList.add('collapsed');
44+
document.body.classList.add('devpanel-collapsed');
45+
}
46+
47+
// Header with toggle button
48+
this.header = document.createElement('div');
49+
this.header.className = 'devpanel-header';
50+
this.header.innerHTML = `
51+
<button class="devpanel-toggle" title="Toggle DevPanel (Ctrl+Shift+D)">
52+
<span class="devpanel-toggle-icon">${this.isCollapsed ? '◀' : '▶'}</span>
53+
</button>
54+
<span class="devpanel-title">Geoman DevPanel</span>
55+
`;
56+
this.container.appendChild(this.header);
57+
58+
// Toggle button handler
59+
this.header
60+
.querySelector('.devpanel-toggle')
61+
.addEventListener('click', () => {
62+
this.toggle();
63+
});
64+
65+
// Content area for modules
66+
this.content = document.createElement('div');
67+
this.content.className = 'devpanel-content';
68+
this.container.appendChild(this.content);
69+
70+
// Add to document
71+
document.body.appendChild(this.container);
72+
}
73+
74+
/**
75+
* Register a module with the panel
76+
*/
77+
registerModule(module) {
78+
if (!module.name) {
79+
throw new Error('Module must have a name property');
80+
}
81+
82+
this.modules.set(module.name, module);
83+
84+
// Create module section
85+
const section = document.createElement('div');
86+
section.className = 'devpanel-module';
87+
section.dataset.module = module.name;
88+
89+
// Check if this module was expanded before
90+
const wasExpanded = this._getModuleState(module.name);
91+
92+
// Module header (collapsible)
93+
const header = document.createElement('div');
94+
header.className = 'devpanel-module-header';
95+
header.innerHTML = `
96+
<span class="devpanel-module-icon">${wasExpanded ? '▼' : '▶'}</span>
97+
<span class="devpanel-module-title">${module.title || module.name}</span>
98+
`;
99+
header.addEventListener('click', () => {
100+
this._toggleModule(module.name);
101+
});
102+
section.appendChild(header);
103+
104+
// Module content
105+
const content = document.createElement('div');
106+
content.className = 'devpanel-module-content';
107+
if (!wasExpanded) {
108+
content.style.display = 'none';
109+
}
110+
section.appendChild(content);
111+
112+
this.content.appendChild(section);
113+
114+
// Initialize the module
115+
module.init(this.map, content, this);
116+
117+
return this;
118+
}
119+
120+
/**
121+
* Toggle a module's expanded state
122+
*/
123+
_toggleModule(moduleName) {
124+
const section = this.content.querySelector(`[data-module="${moduleName}"]`);
125+
if (!section) return;
126+
127+
const content = section.querySelector('.devpanel-module-content');
128+
const icon = section.querySelector('.devpanel-module-icon');
129+
const isExpanded = content.style.display !== 'none';
130+
131+
content.style.display = isExpanded ? 'none' : 'block';
132+
icon.textContent = isExpanded ? '▶' : '▼';
133+
134+
this._setModuleState(moduleName, !isExpanded);
135+
this._saveState();
136+
}
137+
138+
/**
139+
* Expand a specific module
140+
*/
141+
expandModule(moduleName) {
142+
const section = this.content.querySelector(`[data-module="${moduleName}"]`);
143+
if (!section) return;
144+
145+
const content = section.querySelector('.devpanel-module-content');
146+
const icon = section.querySelector('.devpanel-module-icon');
147+
148+
content.style.display = 'block';
149+
icon.textContent = '▼';
150+
151+
this._setModuleState(moduleName, true);
152+
this._saveState();
153+
}
154+
155+
/**
156+
* Collapse a specific module
157+
*/
158+
collapseModule(moduleName) {
159+
const section = this.content.querySelector(`[data-module="${moduleName}"]`);
160+
if (!section) return;
161+
162+
const content = section.querySelector('.devpanel-module-content');
163+
const icon = section.querySelector('.devpanel-module-icon');
164+
165+
content.style.display = 'none';
166+
icon.textContent = '▶';
167+
168+
this._setModuleState(moduleName, false);
169+
this._saveState();
170+
}
171+
172+
/**
173+
* Toggle the panel collapsed state
174+
*/
175+
toggle() {
176+
this.isCollapsed = !this.isCollapsed;
177+
this.container.style.width = this.isCollapsed
178+
? '40px'
179+
: `${this.options.width}px`;
180+
this.container.classList.toggle('collapsed', this.isCollapsed);
181+
182+
// Toggle body class for layout adjustments
183+
document.body.classList.toggle('devpanel-collapsed', this.isCollapsed);
184+
185+
const toggleIcon = this.header.querySelector('.devpanel-toggle-icon');
186+
toggleIcon.textContent = this.isCollapsed ? '◀' : '▶';
187+
188+
this._saveState();
189+
}
190+
191+
/**
192+
* Show the panel
193+
*/
194+
show() {
195+
if (this.isCollapsed) {
196+
this.toggle();
197+
}
198+
}
199+
200+
/**
201+
* Hide the panel
202+
*/
203+
hide() {
204+
if (!this.isCollapsed) {
205+
this.toggle();
206+
}
207+
}
208+
209+
/**
210+
* Get a registered module by name
211+
*/
212+
getModule(name) {
213+
return this.modules.get(name);
214+
}
215+
216+
/**
217+
* Setup keyboard shortcuts
218+
*/
219+
_setupKeyboardShortcuts() {
220+
document.addEventListener('keydown', (e) => {
221+
// Ctrl+Shift+D - Toggle panel
222+
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
223+
e.preventDefault();
224+
this.toggle();
225+
}
226+
});
227+
}
228+
229+
/**
230+
* Load state from localStorage
231+
*/
232+
_loadState() {
233+
if (!this.options.persistState) return;
234+
235+
try {
236+
const state = localStorage.getItem(this.options.storageKey);
237+
if (state) {
238+
this._state = JSON.parse(state);
239+
this.isCollapsed = this._state.collapsed ?? this.options.collapsed;
240+
} else {
241+
this._state = { collapsed: this.options.collapsed, modules: {} };
242+
}
243+
} catch (e) {
244+
this._state = { collapsed: this.options.collapsed, modules: {} };
245+
}
246+
}
247+
248+
/**
249+
* Save state to localStorage
250+
*/
251+
_saveState() {
252+
if (!this.options.persistState) return;
253+
254+
try {
255+
this._state.collapsed = this.isCollapsed;
256+
localStorage.setItem(
257+
this.options.storageKey,
258+
JSON.stringify(this._state)
259+
);
260+
} catch (e) {
261+
// Ignore storage errors
262+
}
263+
}
264+
265+
/**
266+
* Get a module's expanded state
267+
*/
268+
_getModuleState(moduleName) {
269+
return this._state?.modules?.[moduleName] ?? true; // Default to expanded
270+
}
271+
272+
/**
273+
* Set a module's expanded state
274+
*/
275+
_setModuleState(moduleName, expanded) {
276+
if (!this._state) this._state = { modules: {} };
277+
if (!this._state.modules) this._state.modules = {};
278+
this._state.modules[moduleName] = expanded;
279+
}
280+
281+
/**
282+
* Destroy the panel and clean up
283+
*/
284+
destroy() {
285+
// Destroy all modules
286+
this.modules.forEach((module) => {
287+
if (module.destroy) {
288+
module.destroy();
289+
}
290+
});
291+
292+
// Remove container
293+
if (this.container && this.container.parentNode) {
294+
this.container.parentNode.removeChild(this.container);
295+
}
296+
}
297+
}
298+
299+
// Export for use
300+
if (typeof module !== 'undefined' && module.exports) {
301+
module.exports = DevPanel;
302+
} else {
303+
window.DevPanel = DevPanel;
304+
}

0 commit comments

Comments
 (0)