From ef9ee61a7f8afc1ba00f16d63b1fb0c32486a9c6 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Thu, 11 Apr 2024 20:08:55 +0000 Subject: [PATCH] feat(#1122): fix types and make text editable --- backend/src/model/dto/drawings.rs | 4 + doc/decisions/backend_orm_crate.md | 2 +- .../Form/DebouncedFillPatternInput.tsx | 3 +- .../Form/DebouncedSimpleFormTextArea.tsx | 58 ++ frontend/src/config/i18n/de/drawings.json | 1 + frontend/src/config/i18n/en/drawings.json | 1 + .../drawing/DrawingAttributeEditForm.tsx | 130 +++-- .../layers/drawing/DrawingLayer.tsx | 524 +++++++++--------- .../drawing/DrawingLayerFillPatterns.tsx | 10 +- .../drawing/DrawingLayerLabelToolForm.tsx | 4 +- .../drawing/DrawingLayerLeftToolbar.tsx | 114 +++- .../map_planning/layers/drawing/actions.ts | 13 +- .../layers/drawing/labels/EditableText.tsx | 5 +- .../drawing/labels/EditableTextinput.tsx | 2 + .../map_planning/store/UntrackedMapStore.ts | 3 +- .../hooks/useFileExists.ts | 1 - frontend/src/utils/fillPatterns.ts | 8 +- 17 files changed, 561 insertions(+), 322 deletions(-) create mode 100644 frontend/src/components/Form/DebouncedSimpleFormTextArea.tsx diff --git a/backend/src/model/dto/drawings.rs b/backend/src/model/dto/drawings.rs index 39c650f0a..20766c10c 100644 --- a/backend/src/model/dto/drawings.rs +++ b/backend/src/model/dto/drawings.rs @@ -102,6 +102,10 @@ pub enum FillPatternType { Fill, #[serde(rename = "none")] None, + #[serde(rename = "hatch")] + Hatch, + #[serde(rename = "crosshatch")] + CrossHatch, } /// Used to change the `add_date` of a drawing. diff --git a/doc/decisions/backend_orm_crate.md b/doc/decisions/backend_orm_crate.md index a15caae9b..94f54a2be 100644 --- a/doc/decisions/backend_orm_crate.md +++ b/doc/decisions/backend_orm_crate.md @@ -1,4 +1,4 @@ -# Backend ORM/SQL Crate +p# Backend ORM/SQL Crate ## Problem diff --git a/frontend/src/components/Form/DebouncedFillPatternInput.tsx b/frontend/src/components/Form/DebouncedFillPatternInput.tsx index dae4da773..6b9720f7f 100644 --- a/frontend/src/components/Form/DebouncedFillPatternInput.tsx +++ b/frontend/src/components/Form/DebouncedFillPatternInput.tsx @@ -7,6 +7,7 @@ import { SubmitHandler, useFormContext, } from 'react-hook-form'; +import { FillPatternType } from '@/api_types/definitions'; import { DrawingLayerFillPatterns } from '@/features/map_planning/layers/drawing/DrawingLayerFillPatterns'; import { useDebouncedSubmit } from '@/hooks/useDebouncedSubmit'; import CheckIcon from '@/svg/icons/check.svg?react'; @@ -59,7 +60,7 @@ export function DebouncedFillPatternSelector({ )} /> diff --git a/frontend/src/components/Form/DebouncedSimpleFormTextArea.tsx b/frontend/src/components/Form/DebouncedSimpleFormTextArea.tsx new file mode 100644 index 000000000..e6af036e1 --- /dev/null +++ b/frontend/src/components/Form/DebouncedSimpleFormTextArea.tsx @@ -0,0 +1,58 @@ +import { ComponentPropsWithoutRef } from 'react'; +import { + FieldValues, + Path, + SubmitErrorHandler, + SubmitHandler, + useFormContext, +} from 'react-hook-form'; +import { useDebouncedSubmit } from '@/hooks/useDebouncedSubmit'; +import CheckIcon from '@/svg/icons/check.svg?react'; +import CircleDottedIcon from '@/svg/icons/circle-dotted.svg?react'; +import SimpleFormTextArea from './SimpleFormTextArea'; + +type DebouncedSimpleFormTextAreatProps = { + /** A function that takes the from values. + * It is called if the settled value of the input is valid */ + onValid: SubmitHandler; + /** A function that takes the form error. + * It is called if the settled value of the input is invalid */ + onInvalid?: SubmitErrorHandler | undefined; + /** The elements unique id. + * Gets passed to react-hook-form to identify which field the input belongs to. */ + id: Path; + /** The test id for the input */ + 'data-testid'?: string; +} & Omit; + +type SimpleFormInputProps = ComponentPropsWithoutRef; + +export function DebouncedSimpleFormTextArea({ + onValid, + onInvalid, + id, + 'data-testid': testId, + ...rest +}: DebouncedSimpleFormTextAreatProps) { + const { handleSubmit, register, watch } = useFormContext(); + + const submitState = useDebouncedSubmit(watch(id), handleSubmit, onValid, onInvalid); + + return ( +
+ + {submitState === 'loading' && ( + + )} + {submitState === 'idle' && ( + + )} +
+ ); +} diff --git a/frontend/src/config/i18n/de/drawings.json b/frontend/src/config/i18n/de/drawings.json index f709987cf..cbd27827f 100644 --- a/frontend/src/config/i18n/de/drawings.json +++ b/frontend/src/config/i18n/de/drawings.json @@ -19,6 +19,7 @@ "add_date": "Hinzugefügt am", "remove_date": "Entfernt am", "color": "Farbe", + "text": "Text", "strokeWidth": "Stärke", "fillEnabled": "Form füllen", "error_invalid_add_remove_date": "Das Entfernungsdatum muss nach dem Hinzufügungsdatum liegen.", diff --git a/frontend/src/config/i18n/en/drawings.json b/frontend/src/config/i18n/en/drawings.json index d01b4d100..1e25fd1b5 100644 --- a/frontend/src/config/i18n/en/drawings.json +++ b/frontend/src/config/i18n/en/drawings.json @@ -20,6 +20,7 @@ "add_date": "Added on", "remove_date": "Removed on", "color": "Color", + "text": "Text", "strokeWidth": "Stroke Width", "fillEnabled": "Fill Enabled", "error_invalid_add_remove_date": "Remove date must be after add date.", diff --git a/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx b/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx index 54ac401ba..86636f10b 100644 --- a/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx +++ b/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx @@ -8,6 +8,7 @@ import IconButton from '@/components/Button/IconButton'; import SimpleButton, { ButtonVariant } from '@/components/Button/SimpleButton'; import { DebouncedFillPatternSelector } from '@/components/Form/DebouncedFillPatternInput'; import { DebouncedSimpleFormInput } from '@/components/Form/DebouncedSimpleFormInput'; +import { DebouncedSimpleFormTextArea } from '@/components/Form/DebouncedSimpleFormTextArea'; import EditIcon from '@/svg/icons/edit.svg?react'; import EraserIcon from '@/svg/icons/eraser.svg?react'; import useMapStore from '../../store/MapStore'; @@ -18,9 +19,13 @@ const DrawingAttributeEditFormSchema = z .object({ addDate: z.nullable(z.string()).transform((value) => value || undefined), removeDate: z.nullable(z.string()).transform((value) => value || undefined), - color: z.string(), - strokeWidth: z.number().nullable(), - fillPattern: z.nullable(z.string()).transform((value) => value || undefined), + text: z.nullable(z.string()).transform((value) => value || undefined), + color: z.nullable(z.string()).transform((value) => value || undefined), + strokeWidth: z.number().optional().nullable(), + fillPattern: z + .nullable(z.string()) + .optional() + .transform((value) => value || undefined), }) .refine((schema) => !schema.removeDate || !schema.addDate || schema.addDate < schema.removeDate, { path: ['dateRelation'], @@ -30,42 +35,63 @@ export type EditDrawingAttributesProps = { onAddDateChange: (addDate: DrawingFormData) => void; onRemoveDateChange: (removeDate: DrawingFormData) => void; onColorChange: (color: DrawingFormData) => void; + onTextChange: (text: DrawingFormData) => void; onStrokeWidthChange: (strokeWidth: DrawingFormData) => void; onFillPatternChange: (fillPattern: DrawingFormData) => void; onDeleteClick: () => void; isReadOnlyMode: boolean; }; +export type DrawingFormData = + | Pick & { + color?: string; + strokeWidth?: number; + fillPattern?: string; + text?: string; + }; + +export type DrawingFromElement = { + id: string; + type: DrawingShapeType; + addDate?: string; + removeDate?: string; + color?: string; + strokeWidth?: number; + fillPattern?: string; + text?: string; +}; + export type EditSingleDrawingProps = EditDrawingAttributesProps & { - drawing: DrawingDto; + drawing: DrawingFromElement; }; export type EditMultipleDrawingsProps = EditDrawingAttributesProps & { - drawings: DrawingDto[]; + drawings: DrawingFromElement[]; }; export type DrawingAttributeEditFormProps = EditDrawingAttributesProps & { drawingId?: string; addDateDefaultValue: string; removeDateDefaultValue: string; - colorDefaultValue: string; + colorDefaultValue?: string; + textDefaultValue?: string; strokeWidthDefaultValue?: number; fillPatternDefaultValue?: string; multipleDrawings?: boolean; showShapeEditButton: boolean; + showColor: boolean; + showStrokeWidth: boolean; + showFillPattern: boolean; + showText: boolean; }; -export type DrawingFormData = Pick< - DrawingDto, - 'addDate' | 'removeDate' | 'scaleX' | 'scaleY' | 'color' | 'strokeWidth' | 'fillPattern' ->; - export function SingleDrawingAttributeForm({ drawing, onAddDateChange, onRemoveDateChange, onDeleteClick, onColorChange, + onTextChange, onStrokeWidthChange, onFillPatternChange, isReadOnlyMode, @@ -75,18 +101,24 @@ export function SingleDrawingAttributeForm({ ); @@ -114,25 +146,36 @@ export function MultipleDrawingAttributeForm({ return existsCommonDate ? comparisonDate : ''; }; + const hasAnyShapeColor = drawings.some((drawing) => drawing.color !== undefined); + const hasAnyShapeStrokeWidth = drawings.some((drawing) => drawing.strokeWidth !== undefined); + const hasAnyShapeFillPattern = drawings.some((drawing) => drawing.fillPattern !== undefined); + const getCommonColor = () => { - const color = drawings[0].properties?.color; - const existsCommonColor = drawings.every((drawing) => drawing.properties.color === color); - return existsCommonColor ? color : '#000000'; + const color = drawings[0].color; + const existsCommonColor = drawings.every((drawing) => drawing.color === color); + return existsCommonColor ? color : undefined; + }; + + const getCommonText = () => { + const text = drawings[0].text; + const existsCommonText = drawings.every((drawing) => drawing.text === text); + return existsCommonText ? text : undefined; }; const getCommonStrokeWidth = () => { - const strokeWidth = drawings[0].properties?.strokeWidth; + const strokeWidth = drawings[0].strokeWidth; + const existsCommonStrokeWidth = drawings.every( - (drawing) => drawing.properties.strokeWidth === strokeWidth, + (drawing) => drawing.strokeWidth === strokeWidth, ); - return existsCommonStrokeWidth ? strokeWidth : 0; + + return existsCommonStrokeWidth ? strokeWidth : undefined; }; const getCommonFillPattern = () => { - const fillPattern = drawings[0].properties?.fillPattern; - + const fillPattern = drawings[0].fillPattern; const existsCommonFillPattern = drawings.every( - (drawing) => drawing.properties.fillPattern === fillPattern, + (drawing) => drawing.fillPattern === fillPattern, ); return existsCommonFillPattern ? fillPattern : undefined; @@ -146,10 +189,16 @@ export function MultipleDrawingAttributeForm({ colorDefaultValue={getCommonColor()} strokeWidthDefaultValue={getCommonStrokeWidth()} fillPatternDefaultValue={getCommonFillPattern()} + textDefaultValue={getCommonText()} onAddDateChange={onAddDateChange} onRemoveDateChange={onRemoveDateChange} onDeleteClick={onDeleteClick} onColorChange={onColorChange} + onTextChange={onColorChange} + showColor={hasAnyShapeColor} + showStrokeWidth={hasAnyShapeStrokeWidth} + showFillPattern={hasAnyShapeFillPattern} + showText={drawings.every((drawing) => drawing.type === DrawingShapeType.LabelText)} onStrokeWidthChange={onStrokeWidthChange} onFillPatternChange={onFillPatternChange} isReadOnlyMode={isReadOnlyMode} @@ -165,17 +214,23 @@ export function DrawingAttributeEditForm({ addDateDefaultValue, removeDateDefaultValue, colorDefaultValue, + textDefaultValue, strokeWidthDefaultValue, fillPatternDefaultValue, onAddDateChange, onRemoveDateChange, onDeleteClick, onColorChange, + onTextChange, onStrokeWidthChange, onFillPatternChange, isReadOnlyMode, multipleDrawings = false, showShapeEditButton, + showColor, + showStrokeWidth, + showFillPattern, + showText, }: DrawingAttributeEditFormProps) { const { t } = useTranslation(['drawings']); @@ -183,16 +238,14 @@ export function DrawingAttributeEditForm({ defaultValues: { addDate: addDateDefaultValue, removeDate: removeDateDefaultValue, - color: colorDefaultValue, - strokeWidth: strokeWidthDefaultValue, + text: textDefaultValue ?? '', + color: colorDefaultValue ?? '', + strokeWidth: strokeWidthDefaultValue ?? 0, fillPattern: fillPatternDefaultValue, }, resolver: zodResolver(DrawingAttributeEditFormSchema), }); - const showStrokeWidth = strokeWidthDefaultValue !== undefined && strokeWidthDefaultValue > 0; - const showFillPattern = fillPatternDefaultValue !== undefined; - const showColor = colorDefaultValue !== undefined && colorDefaultValue.length > 0; const showProperties = showStrokeWidth || showFillPattern || showColor; const drawingLayerSetEditMode = useMapStore((state) => state.drawingLayerSetEditMode); @@ -204,14 +257,16 @@ export function DrawingAttributeEditForm({ formInfo.reset({ addDate: addDateDefaultValue, removeDate: removeDateDefaultValue, - color: colorDefaultValue, - strokeWidth: strokeWidthDefaultValue, + color: colorDefaultValue ?? '', + text: textDefaultValue ?? '', + strokeWidth: strokeWidthDefaultValue ?? 0, fillPattern: fillPatternDefaultValue, }); }, [ addDateDefaultValue, removeDateDefaultValue, colorDefaultValue, + textDefaultValue, strokeWidthDefaultValue, fillPatternDefaultValue, formInfo, @@ -249,6 +304,19 @@ export function DrawingAttributeEditForm({ {showProperties &&
} + {showText && ( +
+ +
+ )} + {showColor && (
state.selectDrawings); @@ -111,59 +115,27 @@ function DrawingLayer(props: DrawingLayerProps) { const { ...layerProps } = props; - const rectangles = drawingObjects - .filter((object) => object.variant.type === DrawingShapeType.Rectangle) - .map((object) => { - return { - ...object, - properties: object.variant.properties as RectangleProperties, - }; - }); + const rectangles = drawingObjects.filter( + (object) => object.variant.type === DrawingShapeType.Rectangle, + ); - const ellipses = drawingObjects - .filter((object) => object.variant.type === DrawingShapeType.Ellipse) - .map((object) => { - return { - ...object, - properties: object.variant.properties as EllipseProperties, - }; - }); + const ellipses = drawingObjects.filter( + (object) => object.variant.type === DrawingShapeType.Ellipse, + ); - const lines = drawingObjects - .filter((object) => object.variant.type === DrawingShapeType.FreeLine) - .map((object) => { - return { - ...object, - properties: object.variant.properties as FreeLineProperties, - }; - }); + const lines = drawingObjects.filter( + (object) => object.variant.type === DrawingShapeType.FreeLine, + ); - const bezierLines = drawingObjects - .filter((object) => object.variant.type === DrawingShapeType.BezierPolygon) - .map((object) => { - return { - ...object, - properties: object.variant.properties as FreeLineProperties, - }; - }); + const bezierLines = drawingObjects.filter( + (object) => object.variant.type === DrawingShapeType.BezierPolygon, + ); - const textLabels = drawingObjects - .filter((object) => object.variant.type === DrawingShapeType.LabelText) - .map((object) => { - return { - ...object, - properties: object.variant.properties as LabelTextProperties, - }; - }); + const textLabels = drawingObjects.filter( + (object) => object.variant.type === DrawingShapeType.LabelText, + ); - const images = drawingObjects - .filter((object) => object.variant.type === DrawingShapeType.Image) - .map((object) => { - return { - ...object, - properties: object.variant.properties as ImageProperties, - }; - }); + const images = drawingObjects.filter((object) => object.variant.type === DrawingShapeType.Image); const [newLine, setNewLine] = useState(); const [newBezierLine, setNewBezierLine] = useState(); @@ -193,23 +165,22 @@ function DrawingLayer(props: DrawingLayerProps) { { id: uuid.v4(), layerId: getSelectedLayerId() ?? -1, - shapeType: DrawingShapeType.Rectangle, + variant: { + type: DrawingShapeType.Rectangle, + properties: { + width: rectangle.x2 - rectangle.x1, + height: rectangle.y2 - rectangle.y1, + color: rectangle.color, + fillPattern: rectangle.fillPattern, + strokeWidth: rectangle.strokeWidth, + }, + }, rotation: 0, addDate: timelineDate, x: Math.round(rectangle.x1), y: Math.round(rectangle.y1), scaleX: 1, scaleY: 1, - color: rectangle.color, - fillPattern: rectangle.fillPattern, - strokeWidth: rectangle.strokeWidth, - properties: { - width: rectangle.x2 - rectangle.x1, - height: rectangle.y2 - rectangle.y1, - color: rectangle.color, - fillPattern: rectangle.fillPattern, - strokeWidth: rectangle.strokeWidth, - }, }, ]), ); @@ -226,21 +197,20 @@ function DrawingLayer(props: DrawingLayerProps) { layerId: getSelectedLayerId() ?? -1, rotation: 0, addDate: timelineDate, - shapeType: DrawingShapeType.Ellipse, + variant: { + type: DrawingShapeType.Ellipse, + properties: { + radiusX: ellipse.radiusX, + radiusY: ellipse.radiusY, + color: ellipse.color, + fillPattern: ellipse.fillPattern, + strokeWidth: ellipse.strokeWidth, + }, + }, scaleX: 1, scaleY: 1, x: Math.round(ellipse.x), y: Math.round(ellipse.y), - color: ellipse.variant.color, - fillPattern: ellipse.fillPattern, - strokeWidth: ellipse.strokeWidth, - properties: { - radiusX: ellipse.radiusX, - radiusY: ellipse.radiusY, - color: ellipse.variant.color, - fillPattern: ellipse.fillPattern, - strokeWidth: ellipse.strokeWidth, - }, }, ]), ); @@ -257,20 +227,19 @@ function DrawingLayer(props: DrawingLayerProps) { layerId: getSelectedLayerId() ?? -1, rotation: 0, addDate: timelineDate, - shapeType: DrawingShapeType.FreeLine, + variant: { + type: DrawingShapeType.FreeLine, + properties: { + points: line.points, + color: line.color, + fillPattern: line.fillPattern, + strokeWidth: line.strokeWidth, + }, + }, scaleX: 1, scaleY: 1, x: Math.round(line.x), y: Math.round(line.y), - fillPattern: line.fillPattern, - color: line.color, - strokeWidth: line.strokeWidth, - properties: { - points: line.points, - color: line.color, - fillPattern: line.fillPattern, - strokeWidth: line.strokeWidth, - }, }, ]), ); @@ -287,20 +256,19 @@ function DrawingLayer(props: DrawingLayerProps) { layerId: getSelectedLayerId() ?? -1, rotation: 0, addDate: timelineDate, - shapeType: DrawingShapeType.BezierPolygon, + variant: { + type: DrawingShapeType.BezierPolygon, + properties: { + points: line.points, + color: line.color, + fillPattern: line.fillPattern, + strokeWidth: line.strokeWidth, + }, + }, scaleX: 1, scaleY: 1, x: 0, y: 0, - fillPattern: line.fillPattern, - color: line.color, - strokeWidth: line.strokeWidth, - properties: { - points: line.points, - color: line.color, - fillPattern: line.fillPattern, - strokeWidth: line.strokeWidth, - }, }, ]), ); @@ -317,18 +285,19 @@ function DrawingLayer(props: DrawingLayerProps) { layerId: getSelectedLayerId() ?? -1, rotation: 0, addDate: timelineDate, - shapeType: DrawingShapeType.Text, + variant: { + type: DrawingShapeType.LabelText, + properties: { + text: text.text, + color: text.color, + width: 100, + height: 0, + }, + }, scaleX: text.scaleX, scaleY: text.scaleY, x: Math.round(text.x), y: Math.round(text.y), - fillPattern: 'none', - color: text.color, - strokeWidth: 0, - properties: { - text: text.text, - color: text.color, - }, }, ]), ); @@ -345,18 +314,16 @@ function DrawingLayer(props: DrawingLayerProps) { layerId: getSelectedLayerId() ?? -1, rotation: 0, addDate: timelineDate, - shapeType: DrawingShapeType.Image, + variant: { + type: DrawingShapeType.Image, + properties: { + path: image.path, + }, + }, scaleX: image.scaleX, scaleY: image.scaleY, x: Math.round(image.x), y: Math.round(image.y), - fillPattern: 'none', - color: '', - strokeWidth: 0, - properties: { - path: image.path, - color: undefined, - }, }, ]), ); @@ -460,7 +427,7 @@ function DrawingLayer(props: DrawingLayerProps) { points: [], }); } - } else if (selectedShape == DrawingShapeType.Text) { + } else if (selectedShape == DrawingShapeType.LabelText) { if (newLabelText && newLabelText.text.length > 0) { createTextLabel(newLabelText); setNewLabelText(undefined); @@ -726,6 +693,15 @@ function DrawingLayer(props: DrawingLayerProps) { e.target.moveToTop(); }; + const handleUnselectDrawing: KonvaEventListener = useCallback((e) => { + // only unselect if we are clicking on the background + if (e.target instanceof Konva.Shape) { + return; + } + + useMapStore.getState().selectDrawings([]); + }, []); + const handleSelectDrawing: KonvaEventListener = useCallback(() => { const selectedDrawings = (foundDrawings: DrawingDto[], konvaNode: Konva.Node) => { const drawingNode = konvaNode.getAttr('object'); @@ -793,19 +769,25 @@ function DrawingLayer(props: DrawingLayerProps) { const updates = [ { ...bezierLine, - properties: { - points: points, + variant: { + ...bezierLine.variant, + properties: { + ...bezierLine.variant.properties, + points: points, + }, }, }, ]; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore executeAction(new UpdateDrawingAction(updates)); }, [bezierLines, executeAction], ); useEffect(() => { - if (selectedShape != DrawingShapeType.Text && newLabelText) { + if (selectedShape != DrawingShapeType.LabelText && newLabelText) { if (newLabelText.text.length > 0) { createTextLabel(newLabelText); } @@ -830,6 +812,7 @@ function DrawingLayer(props: DrawingLayerProps) { useMapStore.getState().stageRef.current?.on('mousemove.draw', handleMouseMove); useMapStore.getState().stageRef.current?.on('mouseup.endDrawing', handleMouseUp); useMapStore.getState().stageRef.current?.on('mouseup.selectDrawing', handleSelectDrawing); + useMapStore.getState().stageRef.current?.on('click.unselectDrawing', handleUnselectDrawing); transformerActions.addEventListener('dragend.drawings', handleMoveDrawing); transformerActions.addEventListener('transformend.drawings', handleTransformDrawing); @@ -838,6 +821,7 @@ function DrawingLayer(props: DrawingLayerProps) { useMapStore.getState().stageRef.current?.off('mousemove.draw'); useMapStore.getState().stageRef.current?.off('mouseup.endDrawing'); useMapStore.getState().stageRef.current?.off('mouseup.selectDrawing'); + useMapStore.getState().stageRef.current?.off('click.unselectDrawing'); transformerActions.removeEventListener('dragend.drawings'); transformerActions.removeEventListener('transformend.drawings'); }; @@ -848,6 +832,7 @@ function DrawingLayer(props: DrawingLayerProps) { handleMoveDrawing, handleSelectDrawing, handleTransformDrawing, + handleUnselectDrawing, props.listening, ]); @@ -864,26 +849,29 @@ function DrawingLayer(props: DrawingLayerProps) { return ( <> - {bezierLines.map((bezierLine, i) => ( - handleBezierPointsChanged(bezierLine.id, p)} - initialPoints={bezierLine.properties.points} - strokeWidth={bezierLine.strokeWidth} - onLineClick={handleShapeClicked} - color={bezierLine.color} - fillPattern={bezierLine.fillPattern} - x={bezierLine.x} - y={bezierLine.y} - scaleX={bezierLine.scaleX} - scaleY={bezierLine.scaleY} - onDragStart={moveToTop} - > - ))} + {bezierLines.map((bezierLine, i) => { + const properties = bezierLine.variant.properties as PolygonProperties; + return ( + handleBezierPointsChanged(bezierLine.id, p)} + initialPoints={properties.points} + strokeWidth={properties.strokeWidth} + onLineClick={handleShapeClicked} + color={properties.color} + fillPattern={properties.fillPattern} + x={bezierLine.x} + y={bezierLine.y} + scaleX={bezierLine.scaleX} + scaleY={bezierLine.scaleY} + onDragStart={moveToTop} + > + ); + })} {newBezierLine && ( )} - {lines.map((line, i) => ( - - ))} + {lines.map((line, i) => { + const properties = line.variant.properties as FreeLineProperties; + return ( + + ); + })} {newLine && ( )} - {rectangles.map((rectangle, i) => ( - - ))} + {rectangles.map((rectangle, i) => { + const properties = rectangle.variant.properties as RectangleProperties; + return ( + + ); + })} {previewRectangle && ( )} - {ellipses.map((ellipse, i) => ( - - ))} + {ellipses.map((ellipse, i) => { + const properties = ellipse.variant.properties as EllipseProperties; + return ( + + ); + })} {previewEllipse && ( )} - {textLabels.map((text, i) => ( - - ))} + {textLabels.map((text, i) => { + const properties = text.variant.properties as LabelTextProperties; + return ( + + ); + })} {newLabelText && ( { setNewLabelText({ ...newLabelText, text: text }); @@ -1093,20 +1096,23 @@ function DrawingLayer(props: DrawingLayerProps) { /> )} - {images.map((image, i) => ( - - ))} + {images.map((image, i) => { + const properties = image.variant.properties as ImageProperties; + return ( + + ); + })} ); diff --git a/frontend/src/features/map_planning/layers/drawing/DrawingLayerFillPatterns.tsx b/frontend/src/features/map_planning/layers/drawing/DrawingLayerFillPatterns.tsx index d56b1c845..bf9ad0ddb 100644 --- a/frontend/src/features/map_planning/layers/drawing/DrawingLayerFillPatterns.tsx +++ b/frontend/src/features/map_planning/layers/drawing/DrawingLayerFillPatterns.tsx @@ -1,10 +1,10 @@ import { useTranslation } from 'react-i18next'; +import { FillPatternType } from '@/api_types/definitions'; import IconButton from '@/components/Button/IconButton'; import NoFillIcon from '@/svg/icons/forbid.svg?react'; import SquareFilled from '@/svg/icons/square-filled.svg?react'; import TextureIcon from '@/svg/icons/texture.svg?react'; import CrosshatchIcon from '@/svg/icons/track.svg?react'; -import { FillPatternType } from './types'; interface DrawingLayerFillPatternProps { selectedPattern: FillPatternType; @@ -26,7 +26,7 @@ export function DrawingLayerFillPatterns({ isToolboxIcon={true} renderAsActive={selectedPattern === 'none'} onClick={() => { - onChange('none'); + onChange(FillPatternType.None); }} title={t('drawings:draw_free_line_tooltip')} > @@ -36,7 +36,7 @@ export function DrawingLayerFillPatterns({ isToolboxIcon={true} renderAsActive={selectedPattern === 'hatch'} onClick={() => { - onChange('hatch'); + onChange(FillPatternType.Hatch); }} title={t('drawings:draw_free_line_tooltip')} > @@ -46,7 +46,7 @@ export function DrawingLayerFillPatterns({ isToolboxIcon={true} renderAsActive={selectedPattern === 'crosshatch'} onClick={() => { - onChange('crosshatch'); + onChange(FillPatternType.CrossHatch); }} title={t('drawings:draw_rectangle_tooltip')} > @@ -57,7 +57,7 @@ export function DrawingLayerFillPatterns({ isToolboxIcon={true} renderAsActive={selectedPattern === 'fill'} onClick={() => { - onChange('fill'); + onChange(FillPatternType.Fill); }} title={t('drawings:draw_ellipse_tooltip')} > diff --git a/frontend/src/features/map_planning/layers/drawing/DrawingLayerLabelToolForm.tsx b/frontend/src/features/map_planning/layers/drawing/DrawingLayerLabelToolForm.tsx index 83a7c21f0..bceec2136 100644 --- a/frontend/src/features/map_planning/layers/drawing/DrawingLayerLabelToolForm.tsx +++ b/frontend/src/features/map_planning/layers/drawing/DrawingLayerLabelToolForm.tsx @@ -31,9 +31,9 @@ export function DrawingLayerLabelToolForm() {
{ - activateDrawingMode(DrawingShapeType.Text); + activateDrawingMode(DrawingShapeType.LabelText); setStatusPanelContent( , ); diff --git a/frontend/src/features/map_planning/layers/drawing/DrawingLayerLeftToolbar.tsx b/frontend/src/features/map_planning/layers/drawing/DrawingLayerLeftToolbar.tsx index 414fc6134..27b406db6 100644 --- a/frontend/src/features/map_planning/layers/drawing/DrawingLayerLeftToolbar.tsx +++ b/frontend/src/features/map_planning/layers/drawing/DrawingLayerLeftToolbar.tsx @@ -1,7 +1,9 @@ +import { DrawingDto, DrawingShapeType } from '@/api_types/definitions'; import useMapStore from '@/features/map_planning/store/MapStore'; import { useIsReadOnlyMode } from '../../utils/ReadOnlyModeContext'; import { DrawingFormData, + DrawingFromElement as DrawingFormElement, MultipleDrawingAttributeForm, SingleDrawingAttributeForm, } from './DrawingAttributeEditForm'; @@ -52,48 +54,106 @@ export function DrawingLayerLeftToolbar() { }; const onColorChange = ({ color }: DrawingFormData) => { - if (!selectedDrawings?.length) return; + if (!selectedDrawings?.length || color === undefined) return; - const hasChanged = selectedDrawings.some((selectedDrawing) => selectedDrawing.color !== color); + const hasChanged = selectedDrawings.some( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + (selectedDrawing) => selectedDrawing.variant.properties.color !== color, + ); if (!hasChanged) return; const updatedDrawings = selectedDrawings.map((selectedDrawing) => ({ ...selectedDrawing, - color, + variant: { + ...selectedDrawing.variant, + properties: { + ...selectedDrawing.variant.properties, + color, + }, + }, })); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore executeAction(new UpdateDrawingAction(updatedDrawings)); }; const onStrokeWidthChange = ({ strokeWidth }: DrawingFormData) => { - if (!selectedDrawings?.length) return; + if (!selectedDrawings?.length || strokeWidth === undefined || strokeWidth === 0) return; + + const hasChanged = selectedDrawings.some( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + (selectedDrawing) => selectedDrawing.variant.properties.strokeWidth !== strokeWidth, + ); + if (!hasChanged) return; + + const updatedDrawings = selectedDrawings.map((selectedDrawing) => ({ + ...selectedDrawing, + variant: { + ...selectedDrawing.variant, + properties: { + ...selectedDrawing.variant.properties, + strokeWidth, + }, + }, + })); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + executeAction(new UpdateDrawingAction(updatedDrawings)); + }; + + const onTextChange = ({ text }: DrawingFormData) => { + if (!selectedDrawings?.length || text === undefined) return; const hasChanged = selectedDrawings.some( - (selectedDrawing) => selectedDrawing.strokeWidth !== strokeWidth, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + (selectedDrawing) => selectedDrawing.variant.properties.text !== text, ); if (!hasChanged) return; const updatedDrawings = selectedDrawings.map((selectedDrawing) => ({ ...selectedDrawing, - strokeWidth, + variant: { + ...selectedDrawing.variant, + properties: { + ...selectedDrawing.variant.properties, + text, + }, + }, })); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore executeAction(new UpdateDrawingAction(updatedDrawings)); }; const onFillPatternChange = ({ fillPattern }: DrawingFormData) => { - if (!selectedDrawings?.length) return; + if (!selectedDrawings?.length || fillPattern === undefined) return; const hasChanged = selectedDrawings.some( - (selectedDrawing) => selectedDrawing.fillPattern !== fillPattern, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + (selectedDrawing) => selectedDrawing.variant.properties.fillPattern !== fillPattern, ); if (!hasChanged) return; const updatedDrawings = selectedDrawings.map((selectedDrawing) => ({ ...selectedDrawing, - fillPattern, + variant: { + ...selectedDrawing.variant, + properties: { + ...selectedDrawing.variant.properties, + fillPattern, + }, + }, })); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore executeAction(new UpdateDrawingAction(updatedDrawings)); }; @@ -107,25 +167,57 @@ export function DrawingLayerLeftToolbar() { return singeleDrawingSelected ? ( ) : ( ); } + +function mapDtotoFormData(drawing: DrawingDto): DrawingFormElement { + if (drawing.variant.type === DrawingShapeType.Image) { + return { + id: drawing.id, + addDate: drawing.addDate, + removeDate: drawing.removeDate, + type: DrawingShapeType.Image, + }; + } else if (drawing.variant.type === DrawingShapeType.LabelText) { + return { + id: drawing.id, + addDate: drawing.addDate, + removeDate: drawing.removeDate, + type: DrawingShapeType.LabelText, + color: drawing.variant.properties.color, + text: drawing.variant.properties.text, + }; + } else { + return { + id: drawing.id, + addDate: drawing.addDate, + removeDate: drawing.removeDate, + type: drawing.variant.type as DrawingShapeType, + color: drawing.variant.properties.color, + strokeWidth: drawing.variant.properties.strokeWidth, + fillPattern: drawing.variant.properties.fillPattern, + }; + } +} diff --git a/frontend/src/features/map_planning/layers/drawing/actions.ts b/frontend/src/features/map_planning/layers/drawing/actions.ts index 375f45c52..ae3936e8a 100644 --- a/frontend/src/features/map_planning/layers/drawing/actions.ts +++ b/frontend/src/features/map_planning/layers/drawing/actions.ts @@ -38,9 +38,6 @@ export class CreateDrawingAction const newDrawings = this._data.map((newDrawing) => { return { ...newDrawing, - properties: { - ...newDrawing.properties, - }, }; }); @@ -142,6 +139,12 @@ export class UpdateDrawingAction if (updatedDrawing) { return { ...updatedDrawing, + variant: { + ...drawing.variant, + properties: { + ...updatedDrawing.variant.properties, + }, + }, }; } } @@ -156,7 +159,11 @@ export class UpdateDrawingAction ...state.layers, drawing: { ...state.layers.drawing, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore objects: updateDrawings(state.layers.drawing.objects), + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore loadedObjects: updateDrawings(state.layers.drawing.loadedObjects), }, }, diff --git a/frontend/src/features/map_planning/layers/drawing/labels/EditableText.tsx b/frontend/src/features/map_planning/layers/drawing/labels/EditableText.tsx index c17b4cf0c..cb6516065 100644 --- a/frontend/src/features/map_planning/layers/drawing/labels/EditableText.tsx +++ b/frontend/src/features/map_planning/layers/drawing/labels/EditableText.tsx @@ -11,7 +11,7 @@ type EditableTextProps = { scaleX: number; scaleY: number; isEditing: boolean; - onToggleEdit: (e: KonvaEventObject) => void; + onToggleEdit?: (e: KonvaEventObject) => void; onEndEdit?: (e: React.KeyboardEvent) => void; onClick: (e: KonvaEventObject) => void; onChange: (text: string) => void; @@ -37,7 +37,6 @@ export function EditableText({ scaleY, color, object, - width, }: EditableTextProps) { function handleTextChange(e: React.ChangeEvent) { onChange(e.currentTarget.value); @@ -69,10 +68,8 @@ export function EditableText({ listening={true} x={x} y={y} - width={width} scaleX={scaleX} scaleY={scaleY} - height={height} text={text} fontSize={24} fontFamily="Arial" diff --git a/frontend/src/features/map_planning/layers/drawing/labels/EditableTextinput.tsx b/frontend/src/features/map_planning/layers/drawing/labels/EditableTextinput.tsx index 1a8f57a2d..18a060d4d 100644 --- a/frontend/src/features/map_planning/layers/drawing/labels/EditableTextinput.tsx +++ b/frontend/src/features/map_planning/layers/drawing/labels/EditableTextinput.tsx @@ -66,6 +66,8 @@ export function EditableTextInput({ onChange={onChange} onKeyDown={onKeyDown} rows={1} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore style={style} /> diff --git a/frontend/src/features/map_planning/store/UntrackedMapStore.ts b/frontend/src/features/map_planning/store/UntrackedMapStore.ts index 07e3ef98e..392f5d8de 100644 --- a/frontend/src/features/map_planning/store/UntrackedMapStore.ts +++ b/frontend/src/features/map_planning/store/UntrackedMapStore.ts @@ -2,6 +2,7 @@ import Konva from 'konva'; import { Vector2d } from 'konva/lib/types'; import { createRef } from 'react'; import { StateCreator } from 'zustand'; +import { FillPatternType } from '@/api_types/definitions'; import { FrontendOnlyLayerType } from '@/features/map_planning/layers/_frontend_only'; import { SelectionRectAttrs } from '../types/SelectionRectAttrs'; import { convertToDate } from '../utils/date-utils'; @@ -484,7 +485,7 @@ export const createUntrackedMapSlice: StateCreator< }, })); }, - drawingLayerSetFillPattern(fillPattern: string) { + drawingLayerSetFillPattern(fillPattern: FillPatternType) { set((state) => ({ ...state, untrackedState: { diff --git a/frontend/src/features/nextcloud_integration/hooks/useFileExists.ts b/frontend/src/features/nextcloud_integration/hooks/useFileExists.ts index e99f34b49..e571c9028 100644 --- a/frontend/src/features/nextcloud_integration/hooks/useFileExists.ts +++ b/frontend/src/features/nextcloud_integration/hooks/useFileExists.ts @@ -9,7 +9,6 @@ import { getImage } from '@/features/nextcloud_integration/api/getImages'; */ export function useFileExists(path: string) { const webdav = useNextcloudWebDavClient(); - const { isError, isLoading } = useQuery(['webdav', path], { queryFn: () => getImage(path, webdav as WebDAVClient), refetchOnWindowFocus: false, diff --git a/frontend/src/utils/fillPatterns.ts b/frontend/src/utils/fillPatterns.ts index 685f7d2bb..970006943 100644 --- a/frontend/src/utils/fillPatterns.ts +++ b/frontend/src/utils/fillPatterns.ts @@ -1,8 +1,10 @@ -export function getFillPattern(fillPatternType: string, color: string) { +import { FillPatternType } from '@/api_types/definitions'; + +export function getFillPattern(fillPatternType: FillPatternType | undefined, color: string) { switch (fillPatternType) { - case 'hatch': + case FillPatternType.Hatch: return getHatchFillPattern(color); - case 'crosshatch': + case FillPatternType.CrossHatch: return getCrosshatchFillPattern(color); default: return undefined;