Skip to content

Commit c0bbf8c

Browse files
committed
Migrate edition Draw/Modify/Select from OL2 to OL10
* Replace OpenLayers.Control.DrawFeature / ModifyFeature / SelectFeature with OL10 Draw, Modify and Select interactions. The OL2 vector edit layer becomes an OL10 VectorLayer / VectorSource on mainLizmap.map. * Auto-activate Modify after a feature is drawn, including for point layers. Feature highlights use the OL10 draw layer. * Route WMS extent / size queries through the OL10 view. Required because the OL10 map is now the source of truth for the visible viewport during edition. * New modules/Edition.js owns the WKT bridge between the digitizing module and the hidden geometry form field, and emits user-facing toasts via #lizmap-editing-message (the legacy #lizmap-edition-message is still used for save-success / error toasts). * Duplicate toast dispatches removed from legacy/edition.js so the "Edit vertices" / "Draw on the map" toast no longer appears twice. * For point edition the digitizing toolbar is hidden; drawing begins automatically on click. * New baseline locale keys: edition.toolbar.redraw plus four digitizing.toolbar.<tool>.help keys (edit / split / rotate / scaling). * Remove ~120 lines of dead OL2 split helpers from legacy/edition.js (afterReshapeSpliting, beforeFeatureSpliting, afterFeatureSpliting and their editCtrls.reshape / editCtrls.featsplit entries). Builds on the snapping migration. The new digitizing module calls mainLizmap.snapping.reorderSnapInteraction() after adding interactions, so the OL10 Snap follows each new Draw / Modify / Translate. Tests: * 9 spec files updated for OL10 map / interactions (clickOnMapLegacy → clickOnMap, mapOl2 → map, #map → #newOlMap, waitForFunction guards for Select/Modify). * edition-form.spec.js: "must allow modification without creation" now expects #lizmap-editing-message (the new id) for the on-form edit-vertices toast. * editing-copy-paste-geometry.spec.js: mark the 11 interaction tests test.fixme(). They cover behaviour that needs the OL10 GeometryCopy port (lands in the follow-up Copy/Paste PR). * draw.spec.js: mark "Length and angle constraints" test.fixme() — the distance/angle constraint feature is deferred to a follow-up PR. Deferred to follow-up PRs: reshape, parallel offset, move (in edition), distance/angle drawing constraints, split-with-server-save (the digitizing-split button is therefore hidden in edition context for now, remains available in draw context), ReverseGeom port, draw.spec.js new-tool tests and pages/drawpage.js new-tool locators.
1 parent ef2c166 commit c0bbf8c

20 files changed

Lines changed: 1160 additions & 934 deletions

assets/src/components/Digitizing.js

Lines changed: 96 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ export default class Digitizing extends HTMLElement {
6464
this._availableTools = DigitizingAvailableTools.slice(1);
6565
}
6666

67+
/**
68+
* Show an editing message popup for the selected tool
69+
* @param {string} messageKey - The lizDict key for the message
70+
*/
71+
_showEditingMessage(messageKey) {
72+
const msg = lizDict[messageKey];
73+
if (!msg) return;
74+
// Remove any previous editing message
75+
$('#lizmap-editing-message').remove();
76+
lizMap.addMessage(msg, 'info', true, 10000).attr('id', 'lizmap-editing-message');
77+
}
78+
6779
connectedCallback() {
6880

6981
// Update available tools from attribute
@@ -244,34 +256,45 @@ export default class Digitizing extends HTMLElement {
244256
</form>
245257
`;
246258

247-
const mainTemplate = (toolSelected) => html`
259+
const mainTemplate = (toolSelected) => {
260+
// Evaluate on every render so it reflects current edition state
261+
const isEditionPoint = this.context === 'edition' && mainLizmap.edition?.layerGeometry === 'point';
262+
263+
// For point layers in edition, no toolbar needed — drawing starts automatically
264+
if (isEditionPoint) {
265+
this.style.display = 'none';
266+
return html``;
267+
}
268+
this.style.display = '';
269+
270+
return html`
248271
<div class="digitizing">
249-
${toolButtonTemplate(this._availableTools, toolSelected)}
250-
<input
272+
${this.context !== 'edition' ? toolButtonTemplate(this._availableTools, toolSelected) : ''}
273+
${this.context !== 'edition' ? html`<input
251274
type="color"
252275
class="digitizing-color btn"
253276
.value="${mainLizmap.digitizing.drawColor}"
254277
@input=${(event) => mainLizmap.digitizing._userChangedColor(event.target.value)}
255278
data-bs-toggle="tooltip"
256279
data-bs-title="${lizDict['digitizing.toolbar.color']}"
257-
>
258-
<button
280+
>` : ''}
281+
${this.context !== 'edition' ? html`<button
259282
type="button"
260283
class="digitizing-edit btn ${mainLizmap.digitizing.isEdited ? 'active btn-primary' : ''}"
261284
?disabled=${!mainLizmap.digitizing.featureDrawn}
262-
@click=${() => mainLizmap.digitizing.toggleEdit()}
285+
@click=${() => { mainLizmap.digitizing.toggleEdit(); if (mainLizmap.digitizing.isEdited) this._showEditingMessage('digitizing.toolbar.edit.help'); }}
263286
data-bs-toggle="tooltip"
264287
data-bs-title="${lizDict['digitizing.toolbar.edit']}"
265288
>
266289
<svg>
267290
<use href="${lizUrls.svgSprite}#edit"/>
268291
</svg>
269-
</button>
270-
<button
292+
</button>` : ''}
293+
${!isEditionPoint ? html`<button
271294
type="button"
272295
class="digitizing-rotate btn ${mainLizmap.digitizing.isRotate ? 'active btn-primary' : ''}"
273296
?disabled=${!mainLizmap.digitizing.featureDrawn}
274-
@click=${() => mainLizmap.digitizing.toggleRotate()}
297+
@click=${() => { mainLizmap.digitizing.toggleRotate(); if (mainLizmap.digitizing.isRotate) this._showEditingMessage('digitizing.toolbar.rotate.help'); }}
275298
data-bs-toggle="tooltip"
276299
data-bs-title="${lizDict['digitizing.toolbar.rotate']}"
277300
>
@@ -283,27 +306,42 @@ export default class Digitizing extends HTMLElement {
283306
type="button"
284307
class="digitizing-scaling btn ${mainLizmap.digitizing.isScaling ? 'active btn-primary' : ''}"
285308
?disabled=${!mainLizmap.digitizing.featureDrawn}
286-
@click=${() => mainLizmap.digitizing.toggleScaling()}
309+
@click=${() => { mainLizmap.digitizing.toggleScaling(); if (mainLizmap.digitizing.isScaling) this._showEditingMessage('digitizing.toolbar.scaling.help'); }}
287310
data-bs-toggle="tooltip"
288311
data-bs-title="${lizDict['digitizing.toolbar.scaling']}"
289312
>
290313
<svg>
291314
<use href="${lizUrls.svgSprite}#scaling"/>
292315
</svg>
293316
</button>
294-
<button
317+
${this.context !== 'edition' ? html`<button
295318
type="button"
296319
class="digitizing-split btn ${mainLizmap.digitizing.isSplitting ? 'active btn-primary' : ''}"
297320
?disabled=${!mainLizmap.digitizing.featureDrawn}
298-
@click=${() => mainLizmap.digitizing.toggleSplit()}
321+
@click=${() => { mainLizmap.digitizing.toggleSplit(); if (mainLizmap.digitizing.isSplitting) this._showEditingMessage('digitizing.toolbar.split.help'); }}
299322
data-bs-toggle="tooltip"
300323
data-bs-title="${lizDict['digitizing.toolbar.split']}"
301324
>
302325
<svg>
303326
<use href="${lizUrls.svgSprite}#split"/>
304327
</svg>
305-
</button>
306-
<button
328+
</button>` : ''}` : ''}
329+
${this.context === 'edition' && !isEditionPoint ? html`<lizmap-paste-geom></lizmap-paste-geom> <button
330+
type="button"
331+
class="digitizing-restart btn"
332+
?disabled=${!mainLizmap.digitizing.featureDrawn}
333+
@click=${() => {
334+
if (!confirm(lizDict['edition.confirm.restart-drawing'])) return;
335+
mainLizmap.digitizing.eraseAll();
336+
const toolMap = { point: 'point', line: 'line', polygon: 'polygon' };
337+
mainLizmap.digitizing.toolSelected = toolMap[mainLizmap.edition?.layerGeometry] || 'point';
338+
}}
339+
data-bs-toggle="tooltip"
340+
data-bs-title="${lizDict['edition.toolbar.redraw']}"
341+
>
342+
<i class="icon-refresh"></i>
343+
</button>` : ''}
344+
${this.context !== 'edition' ? html`<button
307345
type="button"
308346
class="digitizing-erase btn ${mainLizmap.digitizing.isErasing ? 'active btn-primary' : ''}"
309347
?disabled=${!mainLizmap.digitizing.featureDrawn}
@@ -336,8 +374,8 @@ export default class Digitizing extends HTMLElement {
336374
data-bs-title="${lizDict['tree.button.checkbox']}"
337375
>
338376
<i class="icon-eye-${mainLizmap.digitizing.visibility ? 'open' : 'close'}"></i>
339-
</button>
340-
${this.measureAvailable ? measureButtonTemplate(
377+
</button>` : ''}
378+
${this.measureAvailable && !isEditionPoint ? measureButtonTemplate(
341379
mainLizmap.digitizing.hasMeasureVisible,
342380
) : ''}
343381
${this.saveAvailable ? saveButtonTemplate(
@@ -413,65 +451,29 @@ export default class Digitizing extends HTMLElement {
413451
<div class="digitizing-state hide">
414452
<div class="digitizing-save-state hide">${lizDict['digitizing.toolbar.save.state']}</div>
415453
</div>
416-
<div class="digitizing-constraints ${mainLizmap.digitizing.hasConstraintsPanelVisible ? '' : 'hide'}">
417-
<details>
418-
<summary>
419-
${lizDict['digitizing.constraint.title']}
420-
</summary>
421-
${lizDict['digitizing.constraint.details']}
422-
</details>
423-
<div class="digitizing-constraint-distance input-append">
424-
<input
425-
type="number"
426-
placeholder="${lizDict['digitizing.constraint.distance']}"
427-
class="distance form-control"
428-
min="0"
429-
step="any"
430-
@input=${
431-
event => mainLizmap.digitizing.distanceConstraint = event.target.value
432-
}
433-
>
434-
<span class="add-on">m</span>
435-
</div>
436-
<div class="digitizing-constraint-angle input-append">
437-
<input
438-
type="number"
439-
placeholder="${lizDict['digitizing.constraint.angle']}"
440-
class="angle form-control"
441-
step="any"
442-
@input=${
443-
event => mainLizmap.digitizing.angleConstraint = event.target.value
444-
}
445-
>
446-
<span class="add-on">°</span>
447-
</div>
448-
</div>
449454
${this.textToolsAvailable ? textToolsTemplate(
450455
mainLizmap.digitizing.editedFeatures.length != 0
451456
) : ''}
452457
</div>`;
458+
};
453459

454-
render(
455-
mainTemplate(
456-
this.toolSelected,
457-
),
458-
this,
459-
);
460-
461-
const tooltipTriggerList = this.querySelectorAll('[data-bs-toggle="tooltip"]');
462-
[...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl, {
463-
trigger: 'hover'
464-
}));
460+
this._renderTemplate = () => {
461+
render(mainTemplate(this.toolSelected), this);
462+
this._initTooltips();
463+
this._initDropdowns();
464+
};
465465

466466
mainEventDispatcher.addListener(
467467
() => {
468+
// Sync component tool state with module when context matches
469+
if (mainLizmap.digitizing.context === this.context) {
470+
const moduleTool = mainLizmap.digitizing.toolSelected;
471+
if (this._availableTools.includes(moduleTool)) {
472+
this._toolSelected = moduleTool;
473+
}
474+
}
468475
if (!this.disabled) {
469-
render(
470-
mainTemplate(
471-
this.toolSelected,
472-
),
473-
this,
474-
);
476+
this._renderTemplate();
475477
}
476478
},
477479
[
@@ -495,11 +497,39 @@ export default class Digitizing extends HTMLElement {
495497
'digitizing.visibility',
496498
]
497499
);
500+
501+
this._renderTemplate();
498502
}
499503

500504
disconnectedCallback() {
501505
}
502506

507+
508+
_initTooltips() {
509+
// Dispose existing tooltips to avoid duplicates
510+
this.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
511+
// Skip elements whose title resolves to null (e.g. missing lizDict key)
512+
// to prevent Bootstrap from throwing a type-check error.
513+
const title = el.getAttribute('data-bs-title') || el.getAttribute('title');
514+
if (!title) return;
515+
const existing = bootstrap.Tooltip.getInstance(el);
516+
if (existing) existing.dispose();
517+
new bootstrap.Tooltip(el, { trigger: 'hover' });
518+
});
519+
}
520+
521+
_initDropdowns() {
522+
// Use strategy:'fixed' so Popper positions the dropdown relative to the
523+
// viewport, allowing it to escape overflow:auto containers (#mini-dock).
524+
this.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(el => {
525+
if (!bootstrap.Dropdown.getInstance(el)) {
526+
new bootstrap.Dropdown(el, {
527+
popperConfig: { strategy: 'fixed' }
528+
});
529+
}
530+
});
531+
}
532+
503533
/**
504534
* Digitizing context
505535
* The element attribute: context

0 commit comments

Comments
 (0)