diff options
author | Joris Guyonvarch | 2025-08-31 14:25:43 +0200 |
---|---|---|
committer | Joris Guyonvarch | 2025-08-31 14:25:43 +0200 |
commit | 5ee75f8ab0a1184e2fcb8e8c5aa1db0f01817865 (patch) | |
tree | de2f6a869d51839ed638259c8af57f57bb892473 | |
parent | af06b6ac86d9eccaaebfaa3f66840149cda0b77f (diff) |
Add readonly toggle buttonmain
-rw-r--r-- | frontend/styles/pages/map.sass | 7 | ||||
-rw-r--r-- | frontend/ts/src/lib/leaflet.d.ts | 6 | ||||
-rw-r--r-- | frontend/ts/src/pages/map.ts | 74 | ||||
-rw-r--r-- | frontend/ts/src/pages/map/footer.ts | 25 | ||||
-rw-r--r-- | frontend/ts/src/pages/map/marker.ts | 20 |
5 files changed, 99 insertions, 33 deletions
diff --git a/frontend/styles/pages/map.sass b/frontend/styles/pages/map.sass index 53c5dc4..1ac6c27 100644 --- a/frontend/styles/pages/map.sass +++ b/frontend/styles/pages/map.sass @@ -26,8 +26,15 @@ &__FooterButtons display: flex + align-items: center gap: 1rem + .g-ReadOnly + &--Active + background-color: colors.$green + &:not(.g-ReadOnly--Active) + opacity: 0.2 + .g-ContextMenu z-index: 1000 position: absolute diff --git a/frontend/ts/src/lib/leaflet.d.ts b/frontend/ts/src/lib/leaflet.d.ts index 76b88fd..ab8b038 100644 --- a/frontend/ts/src/lib/leaflet.d.ts +++ b/frontend/ts/src/lib/leaflet.d.ts @@ -40,6 +40,12 @@ export interface Layer { addEventListener: (name: string, fn: (e: MapEvent) => void) => void getLatLng: () => Pos setLatLng: (pos: Pos) => void + dragging: Dragging +} + +export interface Dragging { + enable(): () => void + disable(): () => void } export function tileLayer(url: string): Layer diff --git a/frontend/ts/src/pages/map.ts b/frontend/ts/src/pages/map.ts index b445f63..42da69c 100644 --- a/frontend/ts/src/pages/map.ts +++ b/frontend/ts/src/pages/map.ts @@ -1,4 +1,4 @@ -import { h, Html } from 'lib/rx' +import { h, Html, Var } from 'lib/rx' import * as rx from 'lib/rx' import * as request from 'request' import * as modal from 'ui/modal' @@ -13,9 +13,9 @@ import * as L from 'lib/loadable' import * as layout from 'ui/layout' export function view(id: string): Html { - return rx.withState2<L.Loadable<Map>, L.Loadable<markerModel.Map>>( - [L.loading, L.loading], - (mapVar, markersVar) => { + return rx.withState3<L.Loadable<Map>, L.Loadable<markerModel.Map>, boolean>( + [L.loading, L.loading, false], + (mapVar, markersVar, readOnlyVar) => { request .get<Map>(`/api/maps/${id}`) .then(res => mapVar.update(_ => L.loaded(res))) @@ -23,7 +23,11 @@ export function view(id: string): Html { request .get<Array<markerModel.Marker>>(`/api/markers?map=${id}`) - .then(res => markersVar.update(_ => L.loaded(markerModel.toMap(res)))) + .then(res => { + markersVar.update(_ => L.loaded(markerModel.toMap(res))) + // Set to readonly if there is one marker + if (res.length > 0) readOnlyVar.update(_ => true) + }) .catch(({ message }) => markersVar.update(_ => L.failure(message))) return rx.map2( @@ -33,8 +37,14 @@ export function view(id: string): Html { L.map2([map, markers], (map, markers) => ({ map, markers })), ({map, markers}) => h('div', { className: 'g-Map' }, - withMap(map => mapView(id, map, markers)), - footer.view(map) + withMap(leafletMap => + [ mapView(id, leafletMap, markers, readOnlyVar), + footer.view({ + mapInit: map, + readOnly: readOnlyVar, + onUpdateReadOnly: (b: boolean) => readOnlyVar.update(_ => b) + }) ] + ) ) ) } @@ -74,7 +84,7 @@ interface ErrorModal { message: string } -function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html { +function mapView(map_id: string, map: M.Map, markers: markerModel.Map, readOnlyVar: Var<boolean>): Html { let lastUserAdded: markerModel.Marker | undefined return rx.withState3< @@ -84,9 +94,17 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html { >( [undefined, undefined, undefined], (addModalVar, updateModalVar, errorModalVar) => { - map.addEventListener('click', (event: M.MapEvent) => addModalVar.update(_ => event.latlng)) + map.addEventListener('click', (event: M.MapEvent) => { + if (!readOnlyVar.now()) { + addModalVar.update(_ => event.latlng) + } + }) - const onClick = (m: MarkerContext) => updateModalVar.update(_ => m) + const onClick = (m: MarkerContext) => { + if (!readOnlyVar.now()) { + updateModalVar.update(_ => m) + } + } const onMoveError = (message: string) => { errorModalVar.update(_ => ({ @@ -97,7 +115,7 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html { const elements = M.featureGroup() Object.values(markers).forEach(marker => { - const elem = addMarker({ marker, markers, map, onClick, onMoveError }) + const elem = addMarker({ marker, markers, map, onClick, onMoveError, readOnlyVar }) elements.addLayer(elem) }) @@ -120,7 +138,7 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html { body ), onSuccess: marker => { - addMarker({ marker, markers, map, onClick, onMoveError }) + addMarker({ marker, markers, map, onClick, onMoveError, readOnlyVar }) addModalVar.update(_ => undefined) markers[marker.id] = marker lastUserAdded = marker @@ -145,7 +163,7 @@ function mapView(map_id: string, map: M.Map, markers: markerModel.Map): Html { ), onSuccess: marker => { map.removeLayer(markerElem) - addMarker({ marker, markers, map, onClick, onMoveError }) + addMarker({ marker, markers, map, onClick, onMoveError, readOnlyVar }) updateModalVar.update(_ => undefined) markers[marker.id] = marker lastUserAdded = marker @@ -175,24 +193,30 @@ interface AddMarkerParams { map: M.Map onClick: (m: MarkerContext) => void onMoveError: (message: string) => void + readOnlyVar: Var<boolean> } -function addMarker({ markers, marker, map, onClick, onMoveError }: AddMarkerParams): M.FeatureGroup { +function addMarker({ markers, marker, map, onClick, onMoveError, readOnlyVar }: AddMarkerParams): M.FeatureGroup { const elem = markerView.create({ marker, onMove: markerElem => { - const pos = markerElem.getLatLng() - const newMarker = structuredClone(marker) - newMarker.lat = pos.lat - newMarker.lng = pos.lng - updateMarker(newMarker) - .then(m => markers[marker.id] = m) - .catch(({ message }) => { - markerElem.setLatLng({ lat: marker.lat, lng: marker.lng }) - onMoveError(message) - }) + if (!readOnlyVar.now()) { + const pos = markerElem.getLatLng() + const newMarker = structuredClone(marker) + newMarker.lat = pos.lat + newMarker.lng = pos.lng + updateMarker(newMarker) + .then(m => markers[marker.id] = m) + .catch(({ message }) => { + markerElem.setLatLng({ lat: marker.lat, lng: marker.lng }) + onMoveError(message) + }) + } else { + markerElem.setLatLng({ lat: marker.lat, lng: marker.lng }) + } }, - onClick: (markerElem: M.FeatureGroup) => onClick({ markerId: marker.id, markerElem }) + onClick: (markerElem: M.FeatureGroup) => onClick({ markerId: marker.id, markerElem }), + readOnlyVar }) map.addLayer(elem) diff --git a/frontend/ts/src/pages/map/footer.ts b/frontend/ts/src/pages/map/footer.ts index 945bfd4..f514c66 100644 --- a/frontend/ts/src/pages/map/footer.ts +++ b/frontend/ts/src/pages/map/footer.ts @@ -1,4 +1,4 @@ -import { h, Html } from 'lib/rx' +import { h, Html, Rx } from 'lib/rx' import * as rx from 'lib/rx' import * as request from 'request' import * as modal from 'ui/modal' @@ -7,12 +7,22 @@ import { Map } from 'models/map' import * as form from 'lib/form' import * as L from 'lib/loadable' -export function view(mapInit: Map): Html { +interface ViewParams { + mapInit: Map, + readOnly: Rx<boolean>, + onUpdateReadOnly: (b: boolean) => void, +} + +export function view({ mapInit, readOnly, onUpdateReadOnly }: ViewParams): Html { return h('footer', { className: 'g-Map__Footer' }, rx.withState<Map>(mapInit, mapVar => mapVar.map(map => [ - map.name, + h('div', + { className: 'g-Map__FooterButtons' }, + map.name, + readOnlyButton(readOnly, onUpdateReadOnly) + ), h('div', { className: 'g-Map__FooterButtons' }, viewRenameButton(map, (map: Map) => mapVar.update(_ => map)), @@ -23,6 +33,15 @@ export function view(mapInit: Map): Html { ) } +function readOnlyButton(readOnly: Rx<boolean>, onUpdateReadOnly: (b: boolean) => void): Html { + return h('button', + { className: readOnly.map(b => `g-Button g-ReadOnly ${b ? 'g-ReadOnly--Active' : ''}`), + onclick: readOnly.map(b => (event: Event) => onUpdateReadOnly(!b)) + }, + 'Lecture seule' + ) +} + // Rename modal function viewRenameButton(map: Map, onUpdate: (m: Map) => void): Html { diff --git a/frontend/ts/src/pages/map/marker.ts b/frontend/ts/src/pages/map/marker.ts index b690741..435ed5b 100644 --- a/frontend/ts/src/pages/map/marker.ts +++ b/frontend/ts/src/pages/map/marker.ts @@ -1,16 +1,17 @@ -import { mount, h, s } from 'lib/rx' +import { Var, mount, h, s } from 'lib/rx' import * as Color from 'lib/color' import * as M from 'lib/leaflet' import * as icons from 'lib/icons' import * as markerModel from 'models/marker' interface CreateParams { - marker: markerModel.Marker, - onMove: (marker: M.Layer) => void, - onClick: (markerElem: M.FeatureGroup) => void, + marker: markerModel.Marker + onMove: (marker: M.Layer) => void + onClick: (markerElem: M.FeatureGroup) => void + readOnlyVar: Var<boolean> } -export function create({ marker, onMove, onClick }: CreateParams): M.FeatureGroup { +export function create({ marker, onMove, onClick, readOnlyVar }: CreateParams): M.FeatureGroup { const { lat, lng, color, icon, name, description, radius } = marker const pos = { lat, lng } @@ -30,6 +31,15 @@ export function create({ marker, onMove, onClick }: CreateParams): M.FeatureGrou ? M.featureGroup([ markerElem, circle ]) : M.featureGroup([ markerElem ]) + // Fired before dragging, permits to disable dragging just at the right + // moment if in readonly mode. + markerElem.addEventListener('mousedown', () => { + if (readOnlyVar.now()) { + markerElem.dragging.disable() + window.setTimeout(() => markerElem.dragging.enable()) + } + }) + markerElem.addEventListener('drag', e => { circle && circle.setLatLng(markerElem.getLatLng()) }) |