mirror of
https://github.com/zadam/trilium.git
synced 2026-02-13 01:46:54 +01:00
Compare commits
3 Commits
copilot/sw
...
feature/cu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6d728715f | ||
|
|
54a52f0589 | ||
|
|
badfa23f86 |
@@ -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"
|
||||
|
||||
@@ -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<FNote[]>([]);
|
||||
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 => <NoteWrapper note={note} isReadOnly={isReadOnly} />)}
|
||||
{notes.map(note => <NoteWrapper note={note} isReadOnly={isReadOnly} hideLabels={hideLabels} />)}
|
||||
</Map>}
|
||||
<GeoMapTouchBar state={state} map={apiRef.current} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 <NoteGpxTrack note={note} />;
|
||||
return <NoteGpxTrack note={note} hideLabels={hideLabels} />;
|
||||
}
|
||||
|
||||
if (location) {
|
||||
const latLng = location?.split(",", 2).map((el) => parseFloat(el)) as [ number, number ] | undefined;
|
||||
if (!latLng) return;
|
||||
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} />;
|
||||
return <NoteMarker note={note} editable={!isReadOnly} latLng={latLng} hideLabels={hideLabels} />;
|
||||
}
|
||||
}
|
||||
|
||||
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<string>();
|
||||
const blob = useNoteBlob(note);
|
||||
|
||||
@@ -256,7 +284,7 @@ function NoteGpxTrack({ note }: { note: FNote }) {
|
||||
|
||||
const options = useMemo<GPXOptions>(() => ({
|
||||
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 && <GpxTrack gpxXmlString={xmlString} options={options} />;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<HTMLDivElement>;
|
||||
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<L.Map>(null);
|
||||
const containerRef = useSyncedRef<HTMLDivElement>(_containerRef);
|
||||
|
||||
@@ -49,8 +49,6 @@ export default function Map({ coordinates, zoom, layerName, viewportChanged, chi
|
||||
const [ layer, setLayer ] = useState<Layer>();
|
||||
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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={`geo-map-container ${MAP_LAYERS[layerName].isDarkTheme ? "dark" : ""}`}
|
||||
className={`geo-map-container ${layerData.isDarkTheme ? "dark" : ""}`}
|
||||
>
|
||||
<ParentMap.Provider value={mapRef.current}>
|
||||
{children}
|
||||
|
||||
@@ -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<string, VectorLayer | RasterLayer> = {
|
||||
export const MAP_LAYERS: Record<string, MapLayer> = {
|
||||
"openstreetmap": {
|
||||
name: "OpenStreetMap",
|
||||
type: "raster",
|
||||
|
||||
@@ -226,8 +226,8 @@ function CheckBoxPropertyView({ note, property }: { note: FNote, property: Check
|
||||
<FormListToggleableItem
|
||||
icon={property.icon}
|
||||
title={property.label}
|
||||
currentValue={value}
|
||||
onChange={setValue}
|
||||
currentValue={ property.reverseValue ? !value : value }
|
||||
onChange={newValue => setValue(property.reverseValue ? !newValue : newValue)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface CheckBoxProperty {
|
||||
label: string;
|
||||
bindToLabel: FilterLabelsByType<boolean>;
|
||||
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<ViewTypeOptions, BookConfig> = {
|
||||
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
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user