diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index f434ffeddd1..8609044efa4 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2104,7 +2104,8 @@ "raster": "Raster", "vector_light": "Vector (Light)", "vector_dark": "Vector (Dark)", - "show-scale": "Show scale" + "show-scale": "Show scale", + "show-labels": "Show marker names" }, "table_context_menu": { "delete_row": "Delete row" diff --git a/apps/client/src/widgets/collections/geomap/index.tsx b/apps/client/src/widgets/collections/geomap/index.tsx index 6e490a81e85..8be547fa3a7 100644 --- a/apps/client/src/widgets/collections/geomap/index.tsx +++ b/apps/client/src/widgets/collections/geomap/index.tsx @@ -22,7 +22,7 @@ import { ViewModeProps } from "../interface"; import { createNewNote, moveMarker } from "./api"; import openContextMenu, { openMapContextMenu } from "./context_menu"; import Map from "./map"; -import { DEFAULT_MAP_LAYER_NAME } from "./map_layer"; +import { DEFAULT_MAP_LAYER_NAME, MAP_LAYERS, MapLayer } from "./map_layer"; import Marker, { GpxTrack } from "./marker"; const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659]; @@ -45,10 +45,11 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM const [ state, setState ] = useState(State.Normal); const [ coordinates, setCoordinates ] = useState(viewConfig?.view?.center); const [ zoom, setZoom ] = useState(viewConfig?.view?.zoom); - const [ layerName ] = useNoteLabel(note, "map:style"); const [ hasScale ] = useNoteLabelBoolean(note, "map:scale"); + const [ hideLabels ] = useNoteLabelBoolean(note, "map:hideLabels"); const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly"); const [ notes, setNotes ] = useState([]); + const layerData = useLayerData(note); const spacedUpdate = useSpacedUpdate(() => { if (viewConfig) { saveConfig(viewConfig); @@ -152,7 +153,7 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM apiRef={apiRef} containerRef={containerRef} coordinates={coordinates} zoom={zoom} - layerName={layerName ?? DEFAULT_MAP_LAYER_NAME} + layerData={layerData} viewportChanged={(coordinates, zoom) => { if (!viewConfig) viewConfig = {}; viewConfig.view = { center: coordinates, zoom }; @@ -162,13 +163,35 @@ export default function GeoView({ note, noteIds, viewConfig, saveConfig }: ViewM onContextMenu={onContextMenu} scale={hasScale} > - {notes.map(note => )} + {notes.map(note => )} } ); } +function useLayerData(note: FNote) { + const [ layerName ] = useNoteLabel(note, "map:style"); + // Memo is needed because it would generate unnecessary reloads due to layer change. + const layerData = useMemo(() => { + // Custom layers. + if (layerName?.startsWith("http")) { + return { + name: "Custom", + type: "raster", + url: layerName, + attribution: "" + } satisfies MapLayer; + } + + // Built-in layers. + const layerData = MAP_LAYERS[layerName ?? ""] ?? MAP_LAYERS[DEFAULT_MAP_LAYER_NAME]; + return layerData; + }, [ layerName ]); + + return layerData; +} + function ToggleReadOnlyButton({ note }: { note: FNote }) { const [ isReadOnly, setReadOnly ] = useNoteLabelBoolean(note, "readOnly"); @@ -179,22 +202,26 @@ function ToggleReadOnlyButton({ note }: { note: FNote }) { />; } -function NoteWrapper({ note, isReadOnly }: { note: FNote, isReadOnly: boolean }) { +function NoteWrapper({ note, isReadOnly, hideLabels }: { + note: FNote, + isReadOnly: boolean, + hideLabels: boolean +}) { const mime = useNoteProperty(note, "mime"); const [ location ] = useNoteLabel(note, LOCATION_ATTRIBUTE); if (mime === "application/gpx+xml") { - return ; + return ; } if (location) { const latLng = location?.split(",", 2).map((el) => parseFloat(el)) as [ number, number ] | undefined; if (!latLng) return; - return ; + return ; } } -function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean, latLng: [number, number] }) { +function NoteMarker({ note, editable, latLng, hideLabels }: { note: FNote, editable: boolean, latLng: [number, number], hideLabels: boolean }) { // React to changes const [ color ] = useNoteLabel(note, "color"); const [ iconClass ] = useNoteLabel(note, "iconClass"); @@ -202,8 +229,9 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean const title = useNoteProperty(note, "title"); const icon = useMemo(() => { - return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, title, note.noteId, archived); - }, [ iconClass, color, title, note.noteId, archived]); + const titleOrNone = hideLabels ? undefined : title; + return buildIcon(note.getIcon(), note.getColorClass() ?? undefined, titleOrNone, note.noteId, archived); + }, [ iconClass, color, title, note.noteId, archived, hideLabels ]); const onClick = useCallback(() => { appContext.triggerCommand("openInPopup", { noteIdOrPath: note.noteId }); @@ -235,7 +263,7 @@ function NoteMarker({ note, editable, latLng }: { note: FNote, editable: boolean />; } -function NoteGpxTrack({ note }: { note: FNote }) { +function NoteGpxTrack({ note, hideLabels }: { note: FNote, hideLabels?: boolean }) { const [ xmlString, setXmlString ] = useState(); const blob = useNoteBlob(note); @@ -256,7 +284,7 @@ function NoteGpxTrack({ note }: { note: FNote }) { const options = useMemo(() => ({ markers: { - startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title), + startIcon: buildIcon(note.getIcon(), note.getColorClass(), hideLabels ? undefined : note.title), endIcon: buildIcon("bxs-flag-checkered"), wptIcons: { "": buildIcon("bx bx-pin") @@ -265,7 +293,7 @@ function NoteGpxTrack({ note }: { note: FNote }) { polyline_options: { color: note.getLabelValue("color") ?? "blue" } - }), [ color, iconClass ]); + }), [ color, iconClass, hideLabels ]); return xmlString && ; } diff --git a/apps/client/src/widgets/collections/geomap/map.tsx b/apps/client/src/widgets/collections/geomap/map.tsx index 035f863cc83..19a4586c83b 100644 --- a/apps/client/src/widgets/collections/geomap/map.tsx +++ b/apps/client/src/widgets/collections/geomap/map.tsx @@ -1,7 +1,7 @@ import { useEffect, useImperativeHandle, useRef, useState } from "preact/hooks"; import L, { control, LatLng, Layer, LeafletMouseEvent } from "leaflet"; import "leaflet/dist/leaflet.css"; -import { MAP_LAYERS } from "./map_layer"; +import { MAP_LAYERS, type MapLayer } from "./map_layer"; import { ComponentChildren, createContext, RefObject } from "preact"; import { useElementSize, useSyncedRef } from "../../react/hooks"; @@ -12,7 +12,7 @@ interface MapProps { containerRef?: RefObject; coordinates: LatLng | [number, number]; zoom: number; - layerName: string; + layerData: MapLayer; viewportChanged: (coordinates: LatLng, zoom: number) => void; children: ComponentChildren; onClick?: (e: LeafletMouseEvent) => void; @@ -21,7 +21,7 @@ interface MapProps { scale: boolean; } -export default function Map({ coordinates, zoom, layerName, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) { +export default function Map({ coordinates, zoom, layerData, viewportChanged, children, onClick, onContextMenu, scale, apiRef, containerRef: _containerRef, onZoom }: MapProps) { const mapRef = useRef(null); const containerRef = useSyncedRef(_containerRef); @@ -49,8 +49,6 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi const [ layer, setLayer ] = useState(); useEffect(() => { async function load() { - const layerData = MAP_LAYERS[layerName]; - if (layerData.type === "vector") { const style = (typeof layerData.style === "string" ? layerData.style : await layerData.style()); await import("@maplibre/maplibre-gl-leaflet"); @@ -68,7 +66,7 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi } load(); - }, [ layerName ]); + }, [ layerData ]); // Attach layer to the map. useEffect(() => { @@ -139,7 +137,7 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi return (
{children} diff --git a/apps/client/src/widgets/collections/geomap/map_layer.ts b/apps/client/src/widgets/collections/geomap/map_layer.ts index 7b12a10761e..bb5f6174e63 100644 --- a/apps/client/src/widgets/collections/geomap/map_layer.ts +++ b/apps/client/src/widgets/collections/geomap/map_layer.ts @@ -1,20 +1,17 @@ -export interface MapLayer { - name: string; - isDarkTheme?: boolean; -} - -interface VectorLayer extends MapLayer { +export type MapLayer = ({ type: "vector"; style: string | (() => Promise<{}>) -} - -interface RasterLayer extends MapLayer { +} | { type: "raster"; url: string; attribution: string; -} +}) & { + // Common properties + name: string; + isDarkTheme?: boolean; +}; -export const MAP_LAYERS: Record = { +export const MAP_LAYERS: Record = { "openstreetmap": { name: "OpenStreetMap", type: "raster", diff --git a/apps/client/src/widgets/note_bars/CollectionProperties.tsx b/apps/client/src/widgets/note_bars/CollectionProperties.tsx index 5dba675e6d7..7f739e34bbb 100644 --- a/apps/client/src/widgets/note_bars/CollectionProperties.tsx +++ b/apps/client/src/widgets/note_bars/CollectionProperties.tsx @@ -226,8 +226,8 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check setValue(property.reverseValue ? !newValue : newValue)} /> ); } diff --git a/apps/client/src/widgets/ribbon/collection-properties-config.tsx b/apps/client/src/widgets/ribbon/collection-properties-config.tsx index 1f79217e9cb..17bdff50520 100644 --- a/apps/client/src/widgets/ribbon/collection-properties-config.tsx +++ b/apps/client/src/widgets/ribbon/collection-properties-config.tsx @@ -20,6 +20,8 @@ export interface CheckBoxProperty { label: string; bindToLabel: FilterLabelsByType; icon?: string; + /** When true, the checkbox will be checked when the label value is false. Useful when the label represents a "hide" action, without exposing double negatives to the user. */ + reverseValue?: boolean; } export interface ButtonProperty { @@ -156,6 +158,13 @@ export const bookPropertiesConfig: Record = { icon: "bx bx-ruler", type: "checkbox", bindToLabel: "map:scale" + }, + { + label: t("book_properties_config.show-labels"), + icon: "bx bx-label", + type: "checkbox", + bindToLabel: "map:hideLabels", + reverseValue: true } ] }, diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts index b524b409f0f..a73f97d30f7 100644 --- a/packages/commons/src/lib/attribute_names.ts +++ b/packages/commons/src/lib/attribute_names.ts @@ -48,6 +48,7 @@ type Labels = { "calendar:initialDate": string; "map:style": string; "map:scale": boolean; + "map:hideLabels": boolean; "board:groupBy": string; maxNestingDepth: number; includeArchived: boolean;