Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7653cd6
#777 add simple excalidraw addon
PavelOlkhovoi Aug 28, 2026
56a4277
#777 synch excalidraw zoom with map
PavelOlkhovoi Aug 31, 2026
fcaebcc
#777 add background to excalidraw canvas
PavelOlkhovoi Aug 31, 2026
6edd2ce
#777 align draw and map canvases
PavelOlkhovoi Aug 31, 2026
a405b99
#777 hide excalidraw zoom and menu ui via css
PavelOlkhovoi Aug 31, 2026
6ebc2f5
#777 add german language as default
PavelOlkhovoi Aug 31, 2026
814d440
#777 add custom ui for excalidraw
PavelOlkhovoi Aug 31, 2026
0714c30
#777 hide shortcut ui
PavelOlkhovoi Aug 31, 2026
96a360d
#777 add history actions to ui
PavelOlkhovoi Aug 31, 2026
4b63ea0
#777 remove excalidraw canvas when mode off
PavelOlkhovoi Aug 31, 2026
80e7da0
#777 hide excalidraw library
PavelOlkhovoi Aug 31, 2026
311e13a
#777 improve excalidraw ui
PavelOlkhovoi Aug 31, 2026
623ea35
#777 rename excalidraw to annotations addon
PavelOlkhovoi Aug 31, 2026
bf4c5cb
#777 synch annotations mode with geoportal
PavelOlkhovoi Aug 31, 2026
372d6ea
#777 add texts instrument
PavelOlkhovoi Aug 31, 2026
3423945
#777 fix issue with jumping
PavelOlkhovoi Sep 1, 2026
ed0012f
#777 add annotations groups
PavelOlkhovoi Sep 1, 2026
3b9566f
#777 fix position for first draws
PavelOlkhovoi Sep 1, 2026
90605c8
#777 save drawings in local storage
PavelOlkhovoi Sep 2, 2026
462647a
#777 fix getting images from loclstorage
PavelOlkhovoi Sep 2, 2026
8b711f0
#777 move create draw button
PavelOlkhovoi Sep 2, 2026
2074976
#777 show draws in toolbar
PavelOlkhovoi Sep 2, 2026
d216904
#777 fix not active drawings groups
PavelOlkhovoi Sep 2, 2026
eea248a
#777 add in config sync limits that can disable draw canvas
PavelOlkhovoi Sep 2, 2026
4c9216c
#777 not create new draw if there is a draw
PavelOlkhovoi Sep 2, 2026
0b2bbbe
#777 adjust lack button behavior
PavelOlkhovoi Sep 2, 2026
90bb36e
#777 change drawings ui
PavelOlkhovoi Sep 2, 2026
f5e17eb
#777 improve drawing deletion
PavelOlkhovoi Sep 2, 2026
31d6948
#777 intersect excalidraw context menu
PavelOlkhovoi Sep 2, 2026
adcb4a5
#777 fix gap un drawing toolbar
PavelOlkhovoi Sep 2, 2026
ee3da6b
#777 sync shortcut with instruments panel
PavelOlkhovoi Sep 2, 2026
93dcde9
#777 move drawing in tools UI
PavelOlkhovoi Sep 3, 2026
5de2143
#777 add line and arrow
PavelOlkhovoi Sep 3, 2026
da42849
#777 rename first drawing
PavelOlkhovoi Sep 3, 2026
4eb4c69
#777 fix issue with limited excalidraw zoom changing
PavelOlkhovoi Sep 3, 2026
ca2c511
#777 add drawing automatically
PavelOlkhovoi Sep 3, 2026
a9245e8
#777 improve auto drawing creation
PavelOlkhovoi Sep 3, 2026
5cf4819
#777 remove not active drawing styles
PavelOlkhovoi Sep 4, 2026
66bf499
#777 add default zoom range
PavelOlkhovoi Sep 4, 2026
cbe4b8b
#777 start drawing by excalidraw click
PavelOlkhovoi Sep 4, 2026
4dd8f0a
#777 fix map jumping by excalidraw zoom
PavelOlkhovoi Sep 4, 2026
f6813fb
#777 add common styles fora all shapes
PavelOlkhovoi Sep 4, 2026
1ed7c17
#777 save active instrument in area without drawing
PavelOlkhovoi Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import useLeafletZoomControls from "../../../hooks/leaflet/useLeafletZoomControl
import { useDispatchSachdatenInfoText } from "../../../hooks/useDispatchSachdatenInfoText.ts";
import { useFeatureInfoModeCursorStyle } from "../../../hooks/useFeatureInfoModeCursorStyle.ts";
import { useHighlightModeSync } from "../../../hooks/useHighlightModeSync.ts";
import { useAnnotationModeSync } from "../../../hooks/useAnnotationModeSync";
import { useMapStyleReduxSync } from "../../../hooks/useMapStyleReduxSync";
import { useTourRefCollabLabels } from "../../../hooks/useTourRefCollabLabels.tsx";
import { useWindowSize } from "../../../hooks/useWindowSize.ts";
Expand Down Expand Up @@ -258,6 +259,7 @@ const MapWrapper = () => {
useDispatchSachdatenInfoText();
useMapStyleReduxSync();
useHighlightModeSync();
useAnnotationModeSync();

const tourRefLabels = useTourRefCollabLabels();
const { gazData } = useGazData();
Expand Down
2 changes: 2 additions & 0 deletions apps/geoportal/src/app/components/layers/LayerWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import InteractionView from "./InteractionView";
import { shouldShowAdhocLayerInLayerList } from "../../helper/adhoc-feature-utils";
import { useDynamicStylingSync } from "../../hooks/useDynamicStylingSync";
import { useHighlightLayerButton } from "../../hooks/useHighlightLayerButton";
import { useAnnotationLayerButton } from "../../hooks/useAnnotationLayerButton";
import { useComparingLayerButton } from "../../hooks/useComparingLayerButton";
import { useTimeSliderLayerButton } from "../../hooks/useTimeSliderLayerButton";
import { useComparingSelectionReset } from "../../hooks/useComparingSelectionReset";
Expand All @@ -75,6 +76,7 @@ const LayerWrapper = () => {
const dispatch: AppDispatch = useDispatch();
useDynamicStylingSync();
useHighlightLayerButton();
useAnnotationLayerButton();
useComparingLayerButton();
useTimeSliderLayerButton();
useComparingSelectionReset();
Expand Down
16 changes: 16 additions & 0 deletions apps/geoportal/src/app/constants/fachzwillinge/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ export const addonsFachzwilling: FachzwillingRoute = {
},
},
{ kind: "vectorHighlightControl" },
// annotation layer over the map, pinned to the ground so it pans and scales
// with the camera. The control switches who gets the pointer.
{
kind: "annotationOverlay",
config: {
hideMenu: true,
hideZoom: false,
hideTools: true,
hideHelp: true,
hideLibrary: true,
hideHistory: true,
hideWhenOff: true,
storageKey: "carma.geoportal.addons.annotations",
},
},
{ kind: "annotationControl" },
{
kind: "libreTerrain",
config: { appKey: "geoportal", show: "while3dLayersActive" },
Expand Down
38 changes: 38 additions & 0 deletions apps/geoportal/src/app/hooks/useAnnotationLayerButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useDispatch, useSelector } from "react-redux";

import {
ANNOTATION_LAYER_ID,
ANNOTATION_TOOLS_INTERACTION_ID,
useAnnotationLayerRow,
} from "@carma-mapping/addons";

import {
appendLayer,
getLayers,
removeLayer,
updateLayer,
setActiveInteractionButtonID,
setActiveInteractionLayerID,
} from "../store/slices/mapping";

export { ANNOTATION_LAYER_ID };

export function useAnnotationLayerButton() {
const dispatch = useDispatch();
const layers = useSelector(getLayers);

useAnnotationLayerRow({
hasRow: layers.some((layer) => layer.id === ANNOTATION_LAYER_ID),
onAdd: (layer) => {
dispatch(appendLayer(layer));
dispatch(setActiveInteractionLayerID(layer.id));
dispatch(setActiveInteractionButtonID(ANNOTATION_TOOLS_INTERACTION_ID));
},
onUpdate: (layer) => dispatch(updateLayer(layer)),
onRemove: (id) => {
dispatch(removeLayer(id));
dispatch(setActiveInteractionLayerID(null));
dispatch(setActiveInteractionButtonID(null));
},
});
}
51 changes: 51 additions & 0 deletions apps/geoportal/src/app/hooks/useAnnotationModeSync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";

import { useAddonState } from "@carma-mapping/addons";

import { getUIMode, setUIMode, UIMode } from "../store/slices/ui";

/**
* Keeps the annotation exclusive with the app's other map modes, the way
* `useHighlightModeSync` does for the highlighting: measurement, feature info
* and print run through `UIMode`, the highlighting through its own channel, so
* both have to be watched.
*/
export const useAnnotationModeSync = () => {
const dispatch = useDispatch();
const uiMode = useSelector(getUIMode);
const [annotationMode, setAnnotationMode] = useAddonState("annotationMode");
const [highlightMode, setHighlightMode] = useAddonState("highlightMode");
const isAnnotating = annotationMode?.isOn ?? false;
const isHighlighting = highlightMode?.isOn ?? false;

const previousUIMode = useRef(uiMode);
useEffect(() => {
const otherModeStarted =
previousUIMode.current !== uiMode && uiMode !== UIMode.DEFAULT;
previousUIMode.current = uiMode;
if (otherModeStarted) {
setAnnotationMode((previous) => ({ ...(previous ?? {}), isOn: false }));
}
}, [uiMode, setAnnotationMode]);

const previousIsHighlighting = useRef(isHighlighting);
useEffect(() => {
const highlightingStarted =
!previousIsHighlighting.current && isHighlighting;
previousIsHighlighting.current = isHighlighting;
if (highlightingStarted) {
setAnnotationMode((previous) => ({ ...(previous ?? {}), isOn: false }));
}
}, [isHighlighting, setAnnotationMode]);

const previousIsAnnotating = useRef(isAnnotating);
useEffect(() => {
const annotatingStarted = !previousIsAnnotating.current && isAnnotating;
previousIsAnnotating.current = isAnnotating;
if (annotatingStarted) {
dispatch(setUIMode(UIMode.DEFAULT));
setHighlightMode({ isOn: false });
}
}, [isAnnotating, dispatch, setHighlightMode]);
};
5 changes: 5 additions & 0 deletions apps/geoportal/vite.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export default defineConfig({
root: __dirname,
cacheDir: "../../node_modules/.vite/apps/geoportal",

// @excalidraw/excalidraw's entry reads process.env, which the browser has not
define: {
"process.env.IS_PREACT": JSON.stringify("true"),
},

server: {
port: 4200,
host: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPencil } from "@fortawesome/free-solid-svg-icons";
import { Tooltip } from "antd";

import {
Control,
ControlButtonStyler,
type Positions,
} from "@carma-mapping/map-controls-layout";

import type { AddonComponentProps } from "../../lib/registry";
import { useAnnotationActions } from "./annotation-actions";

/** geoportal's topleft column: measurement is 60, highlighting 70, terrain 80 */
const DEFAULT_CONTROL_POSITION: Positions = "topleft";
const DEFAULT_CONTROL_ORDER = 90;

/** active-control blue */
const ACTIVE_COLOR = "#1677ff";

/** the sketch layer's on/off button; separate so a route can drive the mode itself */
export const AnnotationControl = ({
config,
libreMap,
}: AddonComponentProps<"annotationControl">) => {
const { position = DEFAULT_CONTROL_POSITION, order = DEFAULT_CONTROL_ORDER } =
config ?? {};

const { isOn, toggle } = useAnnotationActions();

if (!libreMap) {
return null;
}

return (
<Control position={position} order={order}>
<Tooltip
title={isOn ? "Zeichnen ausschalten" : "Zeichnen einschalten"}
placement="right"
>
<ControlButtonStyler onClick={toggle} dataTestId="annotation-control">
<FontAwesomeIcon
icon={faPencil}
style={isOn ? { color: ACTIVE_COLOR } : undefined}
/>
</ControlButtonStyler>
</Tooltip>
</Control>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useMemo } from "react";

import { useAnnotationActions } from "./annotation-actions";
import {
AnnotationShapeToolbar,
type AnnotationDrawingEntry,
} from "./AnnotationShapeToolbar";
import { DEFAULT_SHAPES } from "./shape-tools";

export const AnnotationInteractionPanel = () => {
const {
isOn,
isLocked,
shape,
setShape,
undo,
redo,
toggleLock,
groups,
activeId,
pickGroup,
zoomToGroup,
deleteGroup,
} = useAnnotationActions();

// the drawing held ready for the next stroke is not one yet, so it has no pill
const drawings = useMemo<AnnotationDrawingEntry[]>(
() =>
groups
.filter((group) => group.coverage)
.map((group, index) => ({
id: group.id,
// only the first one is spelled out, the rest read as a numbered list
label: index === 0 ? `Zeichnung 1` : `${index + 1}`,
name: `Zeichnung ${index + 1}`,
// locked means nothing is being edited, so nothing is picked out
active: !isLocked && group.id === activeId,
})),
[activeId, groups, isLocked]
);

if (!isOn) {
return null;
}

// locked: only the drawing list, the lock and the new-drawing button stay
return (
<AnnotationShapeToolbar
shapes={isLocked ? [] : DEFAULT_SHAPES}
shape={shape}
onShapeChange={setShape}
drawings={drawings}
onPickDrawing={pickGroup}
onZoomDrawing={zoomToGroup}
onDeleteDrawing={deleteGroup}
isLocked={isLocked}
onToggleLock={toggleLock}
onUndo={isLocked ? undefined : undo}
onRedo={isLocked ? undefined : redo}
/>
);
};
Loading
Loading