From ca0d13378d8a6b1a5e58390fcc7db60eb92b7675 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Mon, 8 Apr 2024 11:23:54 +0000 Subject: [PATCH 01/27] add seperate store for timeline --- .../timeline/TimelineDatePicker.tsx | 23 +-- .../map_planning/layers/plant/actions.ts | 23 ++- .../map_planning/routes/MapWrapper.tsx | 16 +- .../map_planning/store/MapStoreTypes.ts | 44 ----- .../map_planning/store/TimeLineStore.ts | 187 ++++++++++++++++++ .../utils/TimelineEventsHelper.ts | 174 ---------------- 6 files changed, 217 insertions(+), 250 deletions(-) create mode 100644 frontend/src/features/map_planning/store/TimeLineStore.ts delete mode 100644 frontend/src/features/map_planning/utils/TimelineEventsHelper.ts diff --git a/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.tsx b/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.tsx index d8ad0b9e5..93d8e3fe2 100644 --- a/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.tsx +++ b/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.tsx @@ -1,11 +1,11 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import useMapStore from '../../store/MapStore'; import { TimelineDailyEvent, TimelineMonthlyEvent, TimelineYearlyEvent, -} from '../../store/MapStoreTypes'; + useTimelineStore, +} from '../../store/TimeLineStore'; import { getShortMonthNameFromNumber } from '../../utils/date-utils'; import ItemSliderPicker from './ItemSliderPicker'; @@ -33,9 +33,9 @@ type TimelineDatePickerProps = { const TimelineDatePicker = ({ onSelectDate, onLoading, defaultDate }: TimelineDatePickerProps) => { const { i18n } = useTranslation(); - const daySliderItems = useMapStore((state) => state.untrackedState.timeLineEvents.daily); - const monthSliderItems = useMapStore((state) => state.untrackedState.timeLineEvents.monthly); - const yearSliderItems = useMapStore((state) => state.untrackedState.timeLineEvents.yearly); + const daySliderItems = useTimelineStore((state) => state.timeLineEvents.daily); + const monthSliderItems = useTimelineStore((state) => state.timeLineEvents.monthly); + const yearSliderItems = useTimelineStore((state) => state.timeLineEvents.yearly); const defaulttDateObject = new Date(defaultDate); const defaultYear = defaulttDateObject.getFullYear(); @@ -257,14 +257,11 @@ const TimelineDatePicker = ({ onSelectDate, onLoading, defaultDate }: TimelineDa }; const updateTimeLineVisibleYears = (from: number, to: number) => { - useMapStore.setState((state) => ({ + useTimelineStore.setState((state) => ({ ...state, - untrackedState: { - ...state.untrackedState, - timeLineVisibleYears: { - from, - to, - }, + timeLineVisibleYears: { + from, + to, }, })); }; @@ -330,7 +327,7 @@ const TimelineDatePicker = ({ onSelectDate, onLoading, defaultDate }: TimelineDa }, [selectedDayItem]); const updateVisibleYears = useCallback(() => { - const timeLineVisibleYears = useMapStore.getState().untrackedState.timeLineVisibleYears; + const timeLineVisibleYears = useTimelineStore.getState().timeLineVisibleYears; if (yearLeftBoundReached.current) { updateTimeLineVisibleYears(timeLineVisibleYears.from - 100, timeLineVisibleYears.to - 100); yearLeftBoundReached.current = false; diff --git a/frontend/src/features/map_planning/layers/plant/actions.ts b/frontend/src/features/map_planning/layers/plant/actions.ts index 55b5c2dfb..b18af809f 100644 --- a/frontend/src/features/map_planning/layers/plant/actions.ts +++ b/frontend/src/features/map_planning/layers/plant/actions.ts @@ -23,12 +23,7 @@ import updateAddDatePlanting, { } from '../../api/plantingApi'; import useMapStore from '../../store/MapStore'; import { Action, TrackedMapState } from '../../store/MapStoreTypes'; -import { - decreaseAddedPlantsForDate, - increaseAddedPlantsForDate, - timlineEventsUpdateAdedDate, - timlineEventsUpdateRemoveDate, -} from '../../utils/TimelineEventsHelper'; +import { useTimelineStore } from '../../store/TimeLineStore'; import { filterVisibleObjects } from '../../utils/filterVisibleObjects'; export class CreatePlantAction @@ -53,7 +48,7 @@ export class CreatePlantAction apply(state: TrackedMapState): TrackedMapState { const timelineDate = useMapStore.getState().untrackedState.timelineDate; - increaseAddedPlantsForDate(this._data[0].addDate || timelineDate); + useTimelineStore.getState().increaseAddedEventsForDate(this._data[0].addDate || timelineDate); return { ...state, @@ -108,7 +103,10 @@ export class DeletePlantAction (obj) => obj.id === deleteActionPayload.id, ); if (plant?.addDate) { - decreaseAddedPlantsForDate(plant.addDate); + useTimelineStore.getState().decreaseAddedEventsForDate(plant.addDate); + } + if (plant?.removeDate) { + useTimelineStore.getState().decreaseRemovedEventsForDate(plant.removeDate); } } @@ -313,7 +311,9 @@ export class UpdateAddDatePlantAction (obj) => obj.id === addDateActionPayload.id, ); if (plant?.addDate && addDateActionPayload.addDate) { - timlineEventsUpdateAdedDate(plant.addDate, addDateActionPayload.addDate); + useTimelineStore + .getState() + .timelineEventsUpdateAddedDate(plant.addDate, addDateActionPayload.addDate); } } @@ -453,7 +453,10 @@ export class UpdateRemoveDatePlantAction const plant = state.layers.plants.loadedObjects.find( (obj) => obj.id === removeDateActionPayload.id, ); - timlineEventsUpdateRemoveDate(plant?.removeDate, removeDateActionPayload.removeDate); + + useTimelineStore + .getState() + .timelineEventsUpdateRemoveDate(plant?.removeDate, removeDateActionPayload.removeDate); } return { diff --git a/frontend/src/features/map_planning/routes/MapWrapper.tsx b/frontend/src/features/map_planning/routes/MapWrapper.tsx index d0b51ff68..a0983a87b 100644 --- a/frontend/src/features/map_planning/routes/MapWrapper.tsx +++ b/frontend/src/features/map_planning/routes/MapWrapper.tsx @@ -20,6 +20,7 @@ import useGetTimeLineData from '../hooks/useGetTimelineData'; import { useMapId } from '../hooks/useMapId'; import useMapStore from '../store/MapStore'; import { handleRemoteAction } from '../store/RemoteActions'; +import { useTimelineStore } from '../store/TimeLineStore'; import { mapEditorSteps, tourOptions } from '../utils/EditorTour'; import { ReadOnlyModeContextProvider } from '../utils/ReadOnlyModeContext'; @@ -31,7 +32,7 @@ function getDefaultLayer(layerType: LayerType, layers?: LayerDto[]) { } const useInitializeTimeline = (mapId: number) => { - const timeLineVisibleYears = useMapStore((state) => state.untrackedState.timeLineVisibleYears); + const timeLineVisibleYears = useTimelineStore((state) => state.timeLineVisibleYears); const timeLineEvents = useGetTimeLineData( mapId, timeLineVisibleYears.from, @@ -40,15 +41,12 @@ const useInitializeTimeline = (mapId: number) => { useEffect(() => { if (timeLineEvents) { - useMapStore.setState((state) => ({ + useTimelineStore.setState((state) => ({ ...state, - untrackedState: { - ...state.untrackedState, - timeLineEvents: { - daily: timeLineEvents.daily, - monthly: timeLineEvents.monthly, - yearly: timeLineEvents.yearly, - }, + timeLineEvents: { + daily: timeLineEvents.daily, + monthly: timeLineEvents.monthly, + yearly: timeLineEvents.yearly, }, })); } diff --git a/frontend/src/features/map_planning/store/MapStoreTypes.ts b/frontend/src/features/map_planning/store/MapStoreTypes.ts index dd35086b1..399c1c393 100644 --- a/frontend/src/features/map_planning/store/MapStoreTypes.ts +++ b/frontend/src/features/map_planning/store/MapStoreTypes.ts @@ -317,15 +317,6 @@ export const UNTRACKED_DEFAULT_STATE: UntrackedMapState = { }), {} as UntrackedLayers, ), - timeLineEvents: { - daily: [], - monthly: [], - yearly: [], - }, - timeLineVisibleYears: { - from: new Date().getFullYear() - 100, - to: new Date().getFullYear() + 100, - }, }; /** @@ -394,36 +385,6 @@ export type TrackedPlantLayerState = { loadedObjects: PlantingDto[]; }; -export type TimelineDailyEvent = { - key: number; - year: number; - month: number; - day: number; - added: number; - removed: number; -}; - -export type TimelineMonthlyEvent = { - key: number; - year: number; - month: number; - added: number; - removed: number; -}; - -export type TimelineYearlyEvent = { - key: number; - year: number; - added: number; - removed: number; -}; - -export type TimeLineEvents = { - daily: TimelineDailyEvent[]; - monthly: TimelineMonthlyEvent[]; - yearly: TimelineYearlyEvent[]; -}; - export type TrackedBaseLayerState = { id: number; layerId: number; @@ -521,11 +482,6 @@ export type UntrackedMapState = { tooltipPosition: { x: number; y: number }; bottomStatusPanelContent: React.ReactNode | null; layers: UntrackedLayers; - timeLineEvents: TimeLineEvents; - timeLineVisibleYears: { - from: number; - to: number; - }; }; /** diff --git a/frontend/src/features/map_planning/store/TimeLineStore.ts b/frontend/src/features/map_planning/store/TimeLineStore.ts new file mode 100644 index 000000000..9b18b6e39 --- /dev/null +++ b/frontend/src/features/map_planning/store/TimeLineStore.ts @@ -0,0 +1,187 @@ +import { create } from 'zustand'; + +export type TimelineDailyEvent = { + key: number; + year: number; + month: number; + day: number; + added: number; + removed: number; +}; + +export type TimelineMonthlyEvent = { + key: number; + year: number; + month: number; + added: number; + removed: number; +}; + +export type TimelineYearlyEvent = { + key: number; + year: number; + added: number; + removed: number; +}; + +export type TimeLineEvents = { + daily: TimelineDailyEvent[]; + monthly: TimelineMonthlyEvent[]; + yearly: TimelineYearlyEvent[]; +}; + +interface TimelineState { + timeLineEvents: TimeLineEvents; + timeLineVisibleYears: { + from: number; + to: number; + }; + decreaseRemovedEventsForDate: (date: string) => void; + increaseRemovedEventsForDate: (date: string) => void; + decreaseAddedEventsForDate: (date: string) => void; + increaseAddedEventsForDate: (date: string) => void; + timelineEventsUpdateRemoveDate: (oldRemoveDate?: string, newRemoveDate?: string) => void; + timelineEventsUpdateAddedDate: (oldAddedDate: string, newAddedDate: string) => void; +} + +export const useTimelineStore = create((set, get) => { + return { + timeLineEvents: { + daily: [], + monthly: [], + yearly: [], + }, + timeLineVisibleYears: { + from: new Date().getFullYear() - 100, + to: new Date().getFullYear() + 100, + }, + decreaseAddedEventsForDate: (date: string) => { + const parsedDate = new Date(date); + const day = parsedDate.getDate(); + const month = parsedDate.getMonth() + 1; + const year = parsedDate.getFullYear(); + + set((state) => ({ + timeLineEvents: { + ...state.timeLineEvents, + yearly: state.timeLineEvents.yearly.map((yearItem) => { + if (yearItem.year === year) { + return { ...yearItem, added: yearItem.added - 1 }; + } + return yearItem; + }), + monthly: state.timeLineEvents.monthly.map((monthItem) => { + if (monthItem.year === year && monthItem.month === month) { + return { ...monthItem, added: monthItem.added - 1 }; + } + return monthItem; + }), + daily: state.timeLineEvents.daily.map((dayItem) => { + if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { + return { ...dayItem, added: dayItem.added - 1 }; + } + return dayItem; + }), + }, + })); + }, + increaseAddedEventsForDate: (date: string) => { + const parsedDate = new Date(date); + const day = parsedDate.getDate(); + const month = parsedDate.getMonth() + 1; + const year = parsedDate.getFullYear(); + + set((state) => ({ + timeLineEvents: { + ...state.timeLineEvents, + yearly: state.timeLineEvents.yearly.map((yearItem) => { + if (yearItem.year === year) { + return { ...yearItem, added: yearItem.added + 1 }; + } + return yearItem; + }), + monthly: state.timeLineEvents.monthly.map((monthItem) => { + if (monthItem.year === year && monthItem.month === month) { + return { ...monthItem, added: monthItem.added + 1 }; + } + return monthItem; + }), + daily: state.timeLineEvents.daily.map((dayItem) => { + if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { + return { ...dayItem, added: dayItem.added + 1 }; + } + return dayItem; + }), + }, + })); + }, + decreaseRemovedEventsForDate: (date: string) => { + const parsedDate = new Date(date); + const day = parsedDate.getDate(); + const month = parsedDate.getMonth() + 1; + const year = parsedDate.getFullYear(); + + set((state) => ({ + timeLineEvents: { + ...state.timeLineEvents, + yearly: state.timeLineEvents.yearly.map((yearItem) => { + if (yearItem.year === year) { + return { ...yearItem, removed: yearItem.removed - 1 }; + } + return yearItem; + }), + monthly: state.timeLineEvents.monthly.map((monthItem) => { + if (monthItem.year === year && monthItem.month === month) { + return { ...monthItem, removed: monthItem.removed - 1 }; + } + return monthItem; + }), + daily: state.timeLineEvents.daily.map((dayItem) => { + if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { + return { ...dayItem, removed: dayItem.removed - 1 }; + } + return dayItem; + }), + }, + })); + }, + increaseRemovedEventsForDate: (date: string) => { + const parsedDate = new Date(date); + const day = parsedDate.getDate(); + const month = parsedDate.getMonth() + 1; + const year = parsedDate.getFullYear(); + + set((state) => ({ + timeLineEvents: { + ...state.timeLineEvents, + yearly: state.timeLineEvents.yearly.map((yearItem) => { + if (yearItem.year === year) { + return { ...yearItem, removed: yearItem.removed + 1 }; + } + return yearItem; + }), + monthly: state.timeLineEvents.monthly.map((monthItem) => { + if (monthItem.year === year && monthItem.month === month) { + return { ...monthItem, removed: monthItem.removed + 1 }; + } + return monthItem; + }), + daily: state.timeLineEvents.daily.map((dayItem) => { + if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { + return { ...dayItem, removed: dayItem.removed + 1 }; + } + return dayItem; + }), + }, + })); + }, + timelineEventsUpdateAddedDate: (oldAddedDate: string, newAddedDate: string) => { + get().decreaseAddedEventsForDate(oldAddedDate); + get().increaseAddedEventsForDate(newAddedDate); + }, + timelineEventsUpdateRemoveDate: (oldRemoveDate?: string, newRemoveDate?: string) => { + if (oldRemoveDate) get().decreaseRemovedEventsForDate(oldRemoveDate); + if (newRemoveDate) get().increaseRemovedEventsForDate(newRemoveDate); + }, + }; +}); diff --git a/frontend/src/features/map_planning/utils/TimelineEventsHelper.ts b/frontend/src/features/map_planning/utils/TimelineEventsHelper.ts deleted file mode 100644 index be1f86569..000000000 --- a/frontend/src/features/map_planning/utils/TimelineEventsHelper.ts +++ /dev/null @@ -1,174 +0,0 @@ -import useMapStore from '../store/MapStore'; - -export function timlineEventsUpdateRemoveDate(oldRemoveDate?: string, newRemoveDate?: string) { - if (oldRemoveDate) decreaseRemovedPlantsForDate(oldRemoveDate); - if (newRemoveDate) increaseRemovedPlantsForDate(newRemoveDate); -} - -export function timlineEventsUpdateAdedDate(oldAddedDate: string, newAddedDate: string) { - decreaseAddedPlantsForDate(oldAddedDate); - increaseAddedPlantsForDate(newAddedDate); -} - -export function decreaseRemovedPlantsForDate(date: string) { - const parsedDate = new Date(date); - const day = parsedDate.getDate(); - const month = parsedDate.getMonth() + 1; - const year = parsedDate.getFullYear(); - - const timelineEvents = useMapStore.getState().untrackedState.timeLineEvents; - - const updatedYearly = timelineEvents.yearly.map((yearItem) => { - if (yearItem.year === year) { - return { ...yearItem, removed: yearItem.removed - 1 }; - } - return yearItem; - }); - - const updatedMonthly = timelineEvents.monthly.map((monthItem) => { - if (monthItem.year === year && monthItem.month === month) { - return { ...monthItem, removed: monthItem.removed - 1 }; - } - return monthItem; - }); - - const updatedDaily = timelineEvents.daily.map((dayItem) => { - if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { - return { ...dayItem, removed: dayItem.removed - 1 }; - } - return dayItem; - }); - - useMapStore.setState({ - untrackedState: { - ...useMapStore.getState().untrackedState, - timeLineEvents: { - yearly: updatedYearly, - monthly: updatedMonthly, - daily: updatedDaily, - }, - }, - }); -} - -export function increaseRemovedPlantsForDate(date: string) { - const parsedDate = new Date(date); - const day = parsedDate.getDate(); - const month = parsedDate.getMonth() + 1; - const year = parsedDate.getFullYear(); - - const timelineEvents = useMapStore.getState().untrackedState.timeLineEvents; - - const updatedYearly = timelineEvents.yearly.map((yearItem) => { - if (yearItem.year === year) { - return { ...yearItem, removed: yearItem.removed + 1 }; - } - return yearItem; - }); - - const updatedMonthly = timelineEvents.monthly.map((monthItem) => { - if (monthItem.year === year && monthItem.month === month) { - return { ...monthItem, removed: monthItem.removed + 1 }; - } - return monthItem; - }); - - const updatedDaily = timelineEvents.daily.map((dayItem) => { - if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { - return { ...dayItem, removed: dayItem.removed + 1 }; - } - return dayItem; - }); - - useMapStore.setState({ - untrackedState: { - ...useMapStore.getState().untrackedState, - timeLineEvents: { - yearly: updatedYearly, - monthly: updatedMonthly, - daily: updatedDaily, - }, - }, - }); -} -export function increaseAddedPlantsForDate(date: string) { - const parsedDate = new Date(date); - const day = parsedDate.getDate(); - const month = parsedDate.getMonth() + 1; - const year = parsedDate.getFullYear(); - - const timelineEvents = useMapStore.getState().untrackedState.timeLineEvents; - - const updatedYearly = timelineEvents.yearly.map((yearItem) => { - if (yearItem.year === year) { - return { ...yearItem, added: yearItem.added + 1 }; - } - return yearItem; - }); - - const updatedMonthly = timelineEvents.monthly.map((monthItem) => { - if (monthItem.year === year && monthItem.month === month) { - return { ...monthItem, added: monthItem.added + 1 }; - } - return monthItem; - }); - - const updatedDaily = timelineEvents.daily.map((dayItem) => { - if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { - return { ...dayItem, added: dayItem.added + 1 }; - } - return dayItem; - }); - - useMapStore.setState({ - untrackedState: { - ...useMapStore.getState().untrackedState, - timeLineEvents: { - yearly: updatedYearly, - monthly: updatedMonthly, - daily: updatedDaily, - }, - }, - }); -} - -export function decreaseAddedPlantsForDate(date: string) { - const parsedDate = new Date(date); - const day = parsedDate.getDate(); - const month = parsedDate.getMonth() + 1; - const year = parsedDate.getFullYear(); - - const timelineEvents = useMapStore.getState().untrackedState.timeLineEvents; - - const updatedYearly = timelineEvents.yearly.map((yearItem) => { - if (yearItem.year === year) { - return { ...yearItem, added: yearItem.added - 1 }; - } - return yearItem; - }); - - const updatedMonthly = timelineEvents.monthly.map((monthItem) => { - if (monthItem.year === year && monthItem.month === month) { - return { ...monthItem, added: monthItem.added - 1 }; - } - return monthItem; - }); - - const updatedDaily = timelineEvents.daily.map((dayItem) => { - if (dayItem.year === year && dayItem.month === month && dayItem.day === day) { - return { ...dayItem, added: dayItem.added - 1 }; - } - return dayItem; - }); - - useMapStore.setState({ - untrackedState: { - ...useMapStore.getState().untrackedState, - timeLineEvents: { - yearly: updatedYearly, - monthly: updatedMonthly, - daily: updatedDaily, - }, - }, - }); -} From fecee0711c77191a77e3c2b52d2d8aa6247f2cdb Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Mon, 8 Apr 2024 12:36:43 +0000 Subject: [PATCH 02/27] test timeline store --- .../map_planning/store/TimeLineStore.test.ts | 299 ++++++++++++++++++ .../map_planning/store/TimeLineStore.ts | 5 + 2 files changed, 304 insertions(+) create mode 100644 frontend/src/features/map_planning/store/TimeLineStore.test.ts diff --git a/frontend/src/features/map_planning/store/TimeLineStore.test.ts b/frontend/src/features/map_planning/store/TimeLineStore.test.ts new file mode 100644 index 000000000..c1482dde8 --- /dev/null +++ b/frontend/src/features/map_planning/store/TimeLineStore.test.ts @@ -0,0 +1,299 @@ +import { useTimelineStore } from './TimeLineStore'; + +// mock the axios api configuration, so that we don't actually send requests to the backend +vi.mock('@/config/axios'); + +describe('TimeLineStore', () => { + it('increased added events from store', () => { + initializeTimeLineEvents(); + + useTimelineStore.getState().increaseAddedEventsForDate('2022-01-01'); + const timeLineState = useTimelineStore.getState(); + + // Check that the added events for 2022-01-01 have been increased + expect(timeLineState.timeLineEvents.daily[0].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.daily[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.daily[0].added).toEqual(1); + + expect(timeLineState.timeLineEvents.monthly[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.monthly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.monthly[0].added).toEqual(1); + + expect(timeLineState.timeLineEvents.yearly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.yearly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.yearly[0].added).toEqual(1); + + // Check that the added events for 2023-01-01 have not been changed + expect(timeLineState.timeLineEvents.daily[1].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.daily[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.daily[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.monthly[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.monthly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.monthly[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.yearly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.yearly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(10); + }); + + it('increased removed events from store', () => { + initializeTimeLineEvents(); + + useTimelineStore.getState().increaseRemovedEventsForDate('2022-01-01'); + const timeLineState = useTimelineStore.getState(); + + // Check that the added events for 2022-01-01 have been increased + expect(timeLineState.timeLineEvents.daily[0].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.daily[0].removed).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.monthly[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.monthly[0].removed).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.yearly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.yearly[0].removed).toEqual(1); + expect(timeLineState.timeLineEvents.yearly[0].added).toEqual(0); + + // Check that the added events for 2023-01-01 have not been changed + expect(timeLineState.timeLineEvents.daily[1].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.daily[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.daily[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.monthly[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.monthly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.monthly[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.yearly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.yearly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(10); + }); + + it('decrease added events from store', () => { + initializeTimeLineEvents(); + + useTimelineStore.getState().decreaseAddedEventsForDate('2023-01-01'); + const timeLineState = useTimelineStore.getState(); + + // Check that the added events for 2023-01-01 have been decreased + expect(timeLineState.timeLineEvents.daily[1].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.daily[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.daily[1].added).toEqual(9); + + expect(timeLineState.timeLineEvents.monthly[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.monthly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.monthly[1].added).toEqual(9); + + expect(timeLineState.timeLineEvents.yearly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.yearly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(9); + + // Check that the added events for 2022-01-01 has not been changed + expect(timeLineState.timeLineEvents.daily[0].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.daily[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.daily[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.monthly[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.monthly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.monthly[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.yearly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.yearly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.yearly[0].added).toEqual(0); + }); + + it('decrease removed events from store', () => { + initializeTimeLineEvents(); + + useTimelineStore.getState().decreaseRemovedEventsForDate('2023-01-01'); + const timeLineState = useTimelineStore.getState(); + + // Check that the added events for 2023-01-01 have been decreased + expect(timeLineState.timeLineEvents.daily[1].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.daily[1].removed).toEqual(9); + expect(timeLineState.timeLineEvents.daily[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.monthly[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.monthly[1].removed).toEqual(9); + expect(timeLineState.timeLineEvents.monthly[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.yearly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.yearly[1].removed).toEqual(9); + expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(10); + + // Check that the added events for 2022-01-01 has not been changed + expect(timeLineState.timeLineEvents.daily[0].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.daily[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.daily[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.monthly[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.monthly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.monthly[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.yearly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.yearly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.yearly[0].added).toEqual(0); + }); + + it('update added date', () => { + initializeTimeLineEvents(); + + useTimelineStore.getState().timelineEventsUpdateAddedDate('2023-01-01', '2022-01-01'); + const timeLineState = useTimelineStore.getState(); + + // Check that the added events for 2023-01-01 have been decreased + expect(timeLineState.timeLineEvents.daily[1].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.daily[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.daily[1].added).toEqual(9); + + expect(timeLineState.timeLineEvents.monthly[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.monthly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.monthly[1].added).toEqual(9); + + expect(timeLineState.timeLineEvents.yearly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.yearly[1].removed).toEqual(10); + expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(9); + + // Check that the added events for 2022-01-01 have been increased + expect(timeLineState.timeLineEvents.daily[0].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.daily[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.daily[0].added).toEqual(1); + + expect(timeLineState.timeLineEvents.monthly[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.monthly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.monthly[0].added).toEqual(1); + + expect(timeLineState.timeLineEvents.yearly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.yearly[0].removed).toEqual(0); + expect(timeLineState.timeLineEvents.yearly[0].added).toEqual(1); + }); +}); + +it('update removed date', () => { + initializeTimeLineEvents(); + + useTimelineStore.getState().timelineEventsUpdateRemoveDate('2023-01-01', '2022-01-01'); + const timeLineState = useTimelineStore.getState(); + + // Check that the added events for 2023-01-01 have been decreased + expect(timeLineState.timeLineEvents.daily[1].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.daily[1].removed).toEqual(9); + expect(timeLineState.timeLineEvents.daily[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.monthly[1].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.monthly[1].removed).toEqual(9); + expect(timeLineState.timeLineEvents.monthly[1].added).toEqual(10); + + expect(timeLineState.timeLineEvents.yearly[1].year).toEqual(2023); + expect(timeLineState.timeLineEvents.yearly[1].removed).toEqual(9); + expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(10); + + // Check that the added events for 2022-01-01 have been increased + expect(timeLineState.timeLineEvents.daily[0].day).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.daily[0].removed).toEqual(1); + expect(timeLineState.timeLineEvents.daily[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.monthly[0].month).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.monthly[0].removed).toEqual(1); + expect(timeLineState.timeLineEvents.monthly[0].added).toEqual(0); + + expect(timeLineState.timeLineEvents.yearly[0].year).toEqual(2022); + expect(timeLineState.timeLineEvents.yearly[0].removed).toEqual(1); + expect(timeLineState.timeLineEvents.yearly[0].added).toEqual(0); +}); + +function initializeTimeLineEvents() { + useTimelineStore.setState( + (state) => ({ + ...state, + timeLineEvents: { + daily: [ + { + key: 1, + day: 1, + month: 1, + year: 2022, + added: 0, + removed: 0, + }, + { + key: 2, + day: 1, + month: 1, + year: 2023, + added: 10, + removed: 10, + }, + ], + monthly: [ + { + key: 11, + month: 1, + year: 2022, + added: 0, + removed: 0, + }, + { + key: 12, + month: 1, + year: 2023, + added: 10, + removed: 10, + }, + ], + yearly: [ + { + key: 111, + year: 2022, + added: 0, + removed: 0, + }, + { + key: 112, + year: 2023, + added: 10, + removed: 10, + }, + ], + }, + }), + false, + ); +} diff --git a/frontend/src/features/map_planning/store/TimeLineStore.ts b/frontend/src/features/map_planning/store/TimeLineStore.ts index 9b18b6e39..16b576aeb 100644 --- a/frontend/src/features/map_planning/store/TimeLineStore.ts +++ b/frontend/src/features/map_planning/store/TimeLineStore.ts @@ -91,6 +91,9 @@ export const useTimelineStore = create((set, get) => { const month = parsedDate.getMonth() + 1; const year = parsedDate.getFullYear(); + console.log('increaseAddedEventsForDate', year, month, day); + console.log(get().timeLineEvents.daily); + set((state) => ({ timeLineEvents: { ...state.timeLineEvents, @@ -114,6 +117,8 @@ export const useTimelineStore = create((set, get) => { }), }, })); + + console.log(get().timeLineEvents.daily); }, decreaseRemovedEventsForDate: (date: string) => { const parsedDate = new Date(date); From 59143910e842aa5ca8b45ff34907e7f8758c862d Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Mon, 8 Apr 2024 12:42:45 +0000 Subject: [PATCH 03/27] fix typos --- .../src/features/map_planning/store/TimeLineStore.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/map_planning/store/TimeLineStore.test.ts b/frontend/src/features/map_planning/store/TimeLineStore.test.ts index c1482dde8..bf1de351a 100644 --- a/frontend/src/features/map_planning/store/TimeLineStore.test.ts +++ b/frontend/src/features/map_planning/store/TimeLineStore.test.ts @@ -4,7 +4,7 @@ import { useTimelineStore } from './TimeLineStore'; vi.mock('@/config/axios'); describe('TimeLineStore', () => { - it('increased added events from store', () => { + it('increase added events from store', () => { initializeTimeLineEvents(); useTimelineStore.getState().increaseAddedEventsForDate('2022-01-01'); @@ -43,7 +43,7 @@ describe('TimeLineStore', () => { expect(timeLineState.timeLineEvents.yearly[1].added).toEqual(10); }); - it('increased removed events from store', () => { + it('increase removed events from store', () => { initializeTimeLineEvents(); useTimelineStore.getState().increaseRemovedEventsForDate('2022-01-01'); From 0913405ee516f73c8dd44891e6de766931140c07 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Mon, 8 Apr 2024 12:43:45 +0000 Subject: [PATCH 04/27] add changelog entry --- doc/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.md b/doc/changelog.md index e20838886..799c5017c 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -43,7 +43,6 @@ Syntax: `- short text describing the change _(Your Name)_` - Added Meeting Agenda&Notes for 25.03.2024 9:00 _(Markus Raab, Moritz)_ - Added Meeting Agenda&Notes for 02.04.2024 9:00 _(Markus Raab, Lukas)_ - _()_ -- _()_ - Add 'Christoph Schreiner' as team member _(Christoph Schreiner)_ - _()_ - Migrate from Jest to Vitest, update Vite to v5, update Node to 20, .env should be .env.local _(Paul)_ @@ -68,6 +67,7 @@ Syntax: `- short text describing the change _(Your Name)_` - Prevent propagating enft key on markdown editor _(Daniel Steinkogler)_ - Enable deletion of selected plants via DEL shortcut _(Daniel Steinkogler)_ - _()_ +- Refactoring of timeline state and add unit tests _(Daniel Steinkogler)_ - _()_ - Add key combinations for map geometry _(Daniel Steinkogler)_ - Add documentation for adding a new field to an entity _(Christoph Schreiner)_ From 64b8e67358b0eb78dc69d5e6b2f6ddaeb1c620c0 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Mon, 8 Apr 2024 13:04:36 +0000 Subject: [PATCH 05/27] fix timeline tests --- .../timeline/TimelineDatePicker.test.tsx | 18 +++++++----------- .../map_planning/store/TimeLineStore.ts | 5 ----- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.test.tsx b/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.test.tsx index 1758a8a34..30914d43b 100644 --- a/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.test.tsx +++ b/frontend/src/features/map_planning/components/timeline/TimelineDatePicker.test.tsx @@ -1,8 +1,7 @@ import { render } from '@testing-library/react'; import ReactTestUtils, { act } from 'react-dom/test-utils'; import { createDays, createYearsAndMonths } from '../../hooks/useGetTimelineData'; -import useMapStore from '../../store/MapStore'; -import { UNTRACKED_DEFAULT_STATE, UntrackedMapSlice } from '../../store/MapStoreTypes'; +import { useTimelineStore } from '../../store/TimeLineStore'; import TimelineDatePicker from './TimelineDatePicker'; const onSelectChange = vi.fn(); @@ -184,21 +183,18 @@ describe('handleYearItemChange', () => { }); function setupTimeline() { - useMapStore.setState(createStoreWithTimelineData()); + useTimelineStore.setState(createStoreWithTimelineData()); } -function createStoreWithTimelineData(): Pick { +function createStoreWithTimelineData() { const { years, months } = createYearsAndMonths(2000, 2021, {}, {}); const days = createDays(2000, 2021, {}); return { - untrackedState: { - ...UNTRACKED_DEFAULT_STATE, - timeLineEvents: { - yearly: years, - monthly: months, - daily: days, - }, + timeLineEvents: { + yearly: years, + monthly: months, + daily: days, }, }; } diff --git a/frontend/src/features/map_planning/store/TimeLineStore.ts b/frontend/src/features/map_planning/store/TimeLineStore.ts index 16b576aeb..9b18b6e39 100644 --- a/frontend/src/features/map_planning/store/TimeLineStore.ts +++ b/frontend/src/features/map_planning/store/TimeLineStore.ts @@ -91,9 +91,6 @@ export const useTimelineStore = create((set, get) => { const month = parsedDate.getMonth() + 1; const year = parsedDate.getFullYear(); - console.log('increaseAddedEventsForDate', year, month, day); - console.log(get().timeLineEvents.daily); - set((state) => ({ timeLineEvents: { ...state.timeLineEvents, @@ -117,8 +114,6 @@ export const useTimelineStore = create((set, get) => { }), }, })); - - console.log(get().timeLineEvents.daily); }, decreaseRemovedEventsForDate: (date: string) => { const parsedDate = new Date(date); From d35ec87157af20c5b9e087259f1860e3f6a5db30 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Sat, 13 Apr 2024 19:47:54 +0200 Subject: [PATCH 06/27] map deletion --- doc/usecases/assigned/map_deletion.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/usecases/assigned/map_deletion.md b/doc/usecases/assigned/map_deletion.md index 7cd91d3a2..70c72ef20 100644 --- a/doc/usecases/assigned/map_deletion.md +++ b/doc/usecases/assigned/map_deletion.md @@ -13,8 +13,11 @@ - **Precondition:** The user has opened the app and is on the map management screen. - **Main success scenario:** - - The user delete a map by selecting it from the list of maps and confirming the deletion. - - If the user decides to undo the deletion within one month, the map is restored. + - The user deletes a map by selecting it from the list of maps. + - The user confirms the deletion with the information: + - a warning that the whole map including all layers will be deleted, but + - it is possible to contact the PermaplanT service team within one month to restore the map. + - Within one month, the PermaplanT service team can restore the map via provided SQL commands. - **Error scenario:** There is an error in the map deletion process and the map is not deleted as intended. In this case, the app displays an error message and allows the user to try again. From a4ea1d58e9109f3615cff9ec93ea66eaa216d2da Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Sat, 13 Apr 2024 19:50:05 +0200 Subject: [PATCH 07/27] add transparency to viewing state --- doc/usecases/assigned/remember_viewing_state.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/usecases/assigned/remember_viewing_state.md b/doc/usecases/assigned/remember_viewing_state.md index 63632fa8b..f99abe086 100644 --- a/doc/usecases/assigned/remember_viewing_state.md +++ b/doc/usecases/assigned/remember_viewing_state.md @@ -17,6 +17,7 @@ He zooms into the map. He scrolls/drags the map's viewport. He sets layer _B_ invisible. + He sets layer _C_ with transparency. He turns off the grid display. He hides the plant labels. He closes the browser, opens it again and logs in. @@ -26,6 +27,7 @@ - zoomed-in on the map - all plants visible on the map's viewport - layer _B_ invisible + - layer _C_ has the same transparency - grid display turned off - plant labels hidden - **Alternative scenario:** From aee5e882dcc320b30085ffe91697eb055ff863e2 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Mon, 15 Apr 2024 08:57:54 +0200 Subject: [PATCH 08/27] improve PR template --- .github/PULL_REQUEST_TEMPLATE.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 35104fbfe..82abd4207 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -23,7 +23,8 @@ We will help you, but we cannot accept PRs that do not fulfill the basics. - [ ] I fully described what my PR does in the documentation @@ -58,9 +59,11 @@ Also the checklist above can be used. But also the PR creator should check these points when getting a PR done: --> -- [ ] I've tested the code -- [ ] I've read through the whole code -- [ ] I've read through the whole documentation +- [ ] I've tested the code via issue description +- [ ] I've tested the code via requirements +- [ ] I've tested the code with concurrency (several browsers on the same map) +- [ ] I've read through the code +- [ ] I've read through the documentation - [ ] I've checked conformity to guidelines - [ ] I've checked conformity to requirements -- [ ] I've checked that the requirements are tested +- [ ] I've checked that the requirements are automatically tested From 870ecea5f95d82a947cf1bfbab9cbde75e830754 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Mon, 15 Apr 2024 08:58:07 +0200 Subject: [PATCH 09/27] add agenda --- doc/meetings/2024_04_15.md | 104 +++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 doc/meetings/2024_04_15.md diff --git a/doc/meetings/2024_04_15.md b/doc/meetings/2024_04_15.md new file mode 100644 index 000000000..9d8fc2f38 --- /dev/null +++ b/doc/meetings/2024_04_15.md @@ -0,0 +1,104 @@ +# Meeting 15.04.2024 + +## Attendees + +- Markus +- Yvonne +- Daniel +- Christoph +- Moritz +- Jannis +- Lukas +- Filip + +Sick: + +- Andrei (Scrum Master) + +## Buddies + +- Daniel & Andrei +- Moritz & Christoph +- Jannis & Lukas + +## Agenda + +- 09:00 start +- protocol: Daniel +- GitLab Tuwien migration +- release v0.4.0 done + - rebase PRs +- testing + .github +- hierarchy: + - automatic hierarchy based on unique name + - usually no new properties or plants (apart from adding to CSV) +- https://github.com/supabase/index_advisor +- branches: + - new dev branch + - rename to mr.permaplant.net + - dev.permaplant.net rename to master.permaplant.net +- scrum with Andrei: + - issues + - reviews -> later + - (manual) e2e tests -> later Christoph+Filip +- last sprint before release +- create issues: + - rework notification system (Jannis+Andrei) + - Jannis: raw SQL +- [sprint plan](https://project.permaplant.net) + +## Tasks for Everyone + +To be done until **Friday 12.04.2024**: + +- update TISS.txt in submissions repo +- buddy talk +- approve meeting PR [#1254](https://pull.permaplant.net/1254/files) +- request and approve for requested reviews [requested reviews](https://pulls.permaplant.net/?q=is%3Aopen+user-review-requested%3A%40me) +- do/update [sprint plan](https://project.permaplant.net) + +## Individual Tasks + +To be done until **Friday 12.04.2024**: + +- Daniel: finish timeline, drawing, meeting refactor layers +- Filip: GitLab, network problem +- Jannis: creation times, how to name branches, tutorial for layer creation (creation/update metadata + what interface should any layer have), review DB Doku from Christoph, notification profilen +- Moritz: meeting refactoring, heatmap rebase, issues restructure, write if all stale branches can be removed +- Christoph: german name issue, SQL heatmap benchmark, hierarchy PR +- Lukas: documenting how to create+test layers, investigate panning performance +- Andrei: create issues + +## Meeting Notes + +We will apply for [netidee funding](https://www.netidee.at/). +If interested write an email to Markus&Yvonne. +If necessary, we will have a meeting to further discuss details. +Needed for application is a 15 second video per person, to see each team member in the team. + +Migration to GitLab: everyone can log in and play around already. +This week are all Issues & PRs still in Github. + +Release: only small bugs missing. + +In future we will have two main branches: + +- `master`: stable branch for releases (mostly bug fixes) +- `dev`: development branch (bigger features, most things) + +The small feature branches stay the same and usually have the dev branch as target + +- When creating PRs: Link to issue and add small summaries to PRs to help Reviewers +- When doing reviews: mention what you did as reviewer. + +Individual tasks (also see above): + +- Andrei: part of the meetings for Reviews to make working together more efficient in near future. +- Lukas proposition: clean up the branches, that are stale and not of use anymore. +- Layers: Small refactorings and features can be done in parallel, and refactoring and doc is positive. Daniel organizes meeting about this. + +Tasks for everyone (see above): + +- Fill up submission repository. +- For this week do README.md. +- Next week TISS.txt. From 78e7145dc98a807522838338f5784b055e1b14b9 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Mon, 15 Apr 2024 08:59:42 +0200 Subject: [PATCH 10/27] remove old meeting notes --- doc/meetings/2024_04_15.md | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/doc/meetings/2024_04_15.md b/doc/meetings/2024_04_15.md index 9d8fc2f38..fce198c17 100644 --- a/doc/meetings/2024_04_15.md +++ b/doc/meetings/2024_04_15.md @@ -70,35 +70,3 @@ To be done until **Friday 12.04.2024**: - Andrei: create issues ## Meeting Notes - -We will apply for [netidee funding](https://www.netidee.at/). -If interested write an email to Markus&Yvonne. -If necessary, we will have a meeting to further discuss details. -Needed for application is a 15 second video per person, to see each team member in the team. - -Migration to GitLab: everyone can log in and play around already. -This week are all Issues & PRs still in Github. - -Release: only small bugs missing. - -In future we will have two main branches: - -- `master`: stable branch for releases (mostly bug fixes) -- `dev`: development branch (bigger features, most things) - -The small feature branches stay the same and usually have the dev branch as target - -- When creating PRs: Link to issue and add small summaries to PRs to help Reviewers -- When doing reviews: mention what you did as reviewer. - -Individual tasks (also see above): - -- Andrei: part of the meetings for Reviews to make working together more efficient in near future. -- Lukas proposition: clean up the branches, that are stale and not of use anymore. -- Layers: Small refactorings and features can be done in parallel, and refactoring and doc is positive. Daniel organizes meeting about this. - -Tasks for everyone (see above): - -- Fill up submission repository. -- For this week do README.md. -- Next week TISS.txt. From ecc4d33ce350bc6747f287c0f97fa67171e2bef5 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Mon, 15 Apr 2024 09:46:57 +0200 Subject: [PATCH 11/27] during meeting --- doc/meetings/2024_04_15.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/doc/meetings/2024_04_15.md b/doc/meetings/2024_04_15.md index fce198c17..5c49ac647 100644 --- a/doc/meetings/2024_04_15.md +++ b/doc/meetings/2024_04_15.md @@ -4,11 +4,14 @@ - Markus - Yvonne -- Daniel - Christoph - Moritz -- Jannis - Lukas +- Daniel +- Jannis + +Not here: + - Filip Sick: @@ -25,23 +28,18 @@ Sick: - 09:00 start - protocol: Daniel -- GitLab Tuwien migration - release v0.4.0 done - rebase PRs -- testing + .github +- GitLab Tuwien migration - hierarchy: - automatic hierarchy based on unique name - usually no new properties or plants (apart from adding to CSV) + - treffen: Christoph und Lukas - https://github.com/supabase/index_advisor -- branches: - - new dev branch - - rename to mr.permaplant.net - - dev.permaplant.net rename to master.permaplant.net - scrum with Andrei: - issues - reviews -> later - (manual) e2e tests -> later Christoph+Filip -- last sprint before release - create issues: - rework notification system (Jannis+Andrei) - Jannis: raw SQL @@ -52,7 +50,7 @@ Sick: To be done until **Friday 12.04.2024**: - update TISS.txt in submissions repo -- buddy talk +- buddy talk: scoping with mistakes - approve meeting PR [#1254](https://pull.permaplant.net/1254/files) - request and approve for requested reviews [requested reviews](https://pulls.permaplant.net/?q=is%3Aopen+user-review-requested%3A%40me) - do/update [sprint plan](https://project.permaplant.net) @@ -70,3 +68,7 @@ To be done until **Friday 12.04.2024**: - Andrei: create issues ## Meeting Notes + +- christoph asked if properties should be able to get overwritten in child ranks of plants -> will be discussed in seperate meeting. Christoph and Lukas should attend +- drawing layer PR will be created so that handling can be tested +- synchronization of nextcloud images has to be implemented From e7876e5b667c3d59bb237e244241f340703f1f26 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Mon, 15 Apr 2024 11:12:42 +0200 Subject: [PATCH 12/27] after meeting --- doc/meetings/2024_04_15.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/doc/meetings/2024_04_15.md b/doc/meetings/2024_04_15.md index 5c49ac647..44ffea832 100644 --- a/doc/meetings/2024_04_15.md +++ b/doc/meetings/2024_04_15.md @@ -59,12 +59,13 @@ To be done until **Friday 12.04.2024**: To be done until **Friday 12.04.2024**: -- Daniel: finish timeline, drawing, meeting refactor layers -- Filip: GitLab, network problem -- Jannis: creation times, how to name branches, tutorial for layer creation (creation/update metadata + what interface should any layer have), review DB Doku from Christoph, notification profilen -- Moritz: meeting refactoring, heatmap rebase, issues restructure, write if all stale branches can be removed -- Christoph: german name issue, SQL heatmap benchmark, hierarchy PR -- Lukas: documenting how to create+test layers, investigate panning performance +- Daniel: finish timeline, drawing +- Filip: GitLab +- Jannis: creation times, create issues: (1) tutorial for layer creation (creation/update metadata + (2) what interface should any layer have), (3) notification profilen (4) drawing layer db design, (5) unify layer design +- Christoph: SQL heatmap benchmark, hierarchy PR +- Lukas: documenting how to create+test layers, create issues: investigate performance (profile) +- Moritz: heatmap rebase, meeting refactoring, issues restructure, write if all stale branches can be removed + - Andrei: create issues ## Meeting Notes @@ -72,3 +73,4 @@ To be done until **Friday 12.04.2024**: - christoph asked if properties should be able to get overwritten in child ranks of plants -> will be discussed in seperate meeting. Christoph and Lukas should attend - drawing layer PR will be created so that handling can be tested - synchronization of nextcloud images has to be implemented +- we shouldn't have to many different tasks in progress. From 4f0b2fe0a5e5fa796e375d55e41eb57912de7e16 Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Mon, 15 Apr 2024 19:13:46 +0200 Subject: [PATCH 13/27] small rework --- doc/changelog.md | 1 + doc/meetings/2024_04_15.md | 25 +++++++++++-------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/doc/changelog.md b/doc/changelog.md index fabb8de44..f6692cb16 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -8,6 +8,7 @@ Syntax: `- short text describing the change _(Your Name)_` ## UNRELEASED +- Added Meeting Agenda&Notes for 15.04.2024 9:00 _(Markus Raab, Daniel)_ - _()_ - _()_ - _()_ diff --git a/doc/meetings/2024_04_15.md b/doc/meetings/2024_04_15.md index 44ffea832..b1d33c871 100644 --- a/doc/meetings/2024_04_15.md +++ b/doc/meetings/2024_04_15.md @@ -29,12 +29,7 @@ Sick: - 09:00 start - protocol: Daniel - release v0.4.0 done - - rebase PRs - GitLab Tuwien migration -- hierarchy: - - automatic hierarchy based on unique name - - usually no new properties or plants (apart from adding to CSV) - - treffen: Christoph und Lukas - https://github.com/supabase/index_advisor - scrum with Andrei: - issues @@ -47,30 +42,32 @@ Sick: ## Tasks for Everyone -To be done until **Friday 12.04.2024**: +To be done until **Friday 19.04.2024**: -- update TISS.txt in submissions repo +- create or update TISS.txt in submissions repo - buddy talk: scoping with mistakes -- approve meeting PR [#1254](https://pull.permaplant.net/1254/files) +- approve [meeting PR](https://pull.permaplant.net/1284/files) - request and approve for requested reviews [requested reviews](https://pulls.permaplant.net/?q=is%3Aopen+user-review-requested%3A%40me) - do/update [sprint plan](https://project.permaplant.net) ## Individual Tasks -To be done until **Friday 12.04.2024**: +To be done until **Friday 19.04.2024**: - Daniel: finish timeline, drawing -- Filip: GitLab +- Filip: GitLab migration - Jannis: creation times, create issues: (1) tutorial for layer creation (creation/update metadata + (2) what interface should any layer have), (3) notification profilen (4) drawing layer db design, (5) unify layer design - Christoph: SQL heatmap benchmark, hierarchy PR - Lukas: documenting how to create+test layers, create issues: investigate performance (profile) - Moritz: heatmap rebase, meeting refactoring, issues restructure, write if all stale branches can be removed - - Andrei: create issues ## Meeting Notes -- christoph asked if properties should be able to get overwritten in child ranks of plants -> will be discussed in seperate meeting. Christoph and Lukas should attend +- great job with release, please rebase your PRs +- christoph asked if properties should be able to get overwritten in child ranks of plants -> will be discussed in separate meeting. Christoph and Lukas should attend. + - automatic hierarchy based on unique name + - usually no new properties or plants (apart from adding to CSV) - drawing layer PR will be created so that handling can be tested -- synchronization of nextcloud images has to be implemented -- we shouldn't have to many different tasks in progress. +- groupfolder for nextcloud images has to be implemented, so that images on one map can be shared between different users +- we shouldn't have to many different tasks in progress but always enough issues created to always have enough for next sprints From 2d7240d5884e32eae0105decf2facc338081439b Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 12:17:21 +0000 Subject: [PATCH 14/27] feat(#1289): add points in middle of polygon --- frontend/src/config/i18n/de/drawings.json | 3 + frontend/src/config/i18n/en/drawings.json | 2 + .../drawing/DrawingAttributeEditForm.tsx | 17 +++ .../layers/drawing/DrawingLayer.tsx | 84 +++++++---- .../layers/drawing/shapes/BezierPolygon.tsx | 124 ++++++++++++----- .../map_planning/types/PolygonTypes.ts | 4 +- .../map_planning/utils/PolygonUtils.ts | 130 ++++++++++-------- 7 files changed, 242 insertions(+), 122 deletions(-) diff --git a/frontend/src/config/i18n/de/drawings.json b/frontend/src/config/i18n/de/drawings.json index 103ab08ae..ec0e01d8f 100644 --- a/frontend/src/config/i18n/de/drawings.json +++ b/frontend/src/config/i18n/de/drawings.json @@ -6,6 +6,7 @@ "draw_bezier_polygon_hint": "Klicken Sie mit der linken Maustaste, um einen Punkt zu setzen. Klicken Sie mit der rechten Maustaste, um die Zeichnung zu beenden.", "edit_bezier_polygon_hint": "Klicken Sie mit der linken Maustaste, um einen Punkt zu setzen.", "delete_bezier_polygon_point_hint": "Klicken Sie auf einen Punkt, um ihn zu löschen.", + "bezier_polygon_add_points_between_hint": "Klicken Sie auf die Map, um einen Punkt hinzuzufügen.", "draw_rectangle_tooltip": "Rechteck zeichnen.", "draw_ellipse_tooltip": "Ellipse zeichnen.", @@ -13,6 +14,8 @@ "draw_bezier_polygon_tooltip": "Freies Polygon zeichnen.", "edit_bezier_polygon_tooltip": "Freies Polygon bearbeiten.", "delete_bezier_polygon_point_tooltip": "Punkte löschen.", + "bezier_polygon_add_points_between_tooltip": "Punkte zwischen zwei Punkten hinzufügen.", + "delete": "Element löschen", "delete_multiple_drawings": "Elemente löschen", diff --git a/frontend/src/config/i18n/en/drawings.json b/frontend/src/config/i18n/en/drawings.json index 02ea51857..36aa3ff06 100644 --- a/frontend/src/config/i18n/en/drawings.json +++ b/frontend/src/config/i18n/en/drawings.json @@ -6,6 +6,7 @@ "draw_bezier_polygon_hint": "Use left-click to add points, right-click to finish current polygon.", "edit_bezier_polygon_hint": "Use left-click to add points.", "delete_bezier_polygon_point_hint": "Click on a point to delete it.", + "bezier_polygon_add_points_between_hint": "Click on the map to add points between two points.", "draw_rectangle_tooltip": "Draw rectangle.", "draw_ellipse_tooltip": "Draw ellipse.", @@ -13,6 +14,7 @@ "draw_bezier_polygon_tooltip": "Draw bezier polygon.", "edit_bezier_polygon_tooltip": "Edit bezier polygon.", "delete_bezier_polygon_point_tooltip": "Delete points.", + "bezier_polygon_add_points_between_tooltip": "Add points between two points.", "delete": "Delete Drawing", "delete_multiple_drawings": "Delete Drawings", diff --git a/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx b/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx index 1cc11b572..58d83a38d 100644 --- a/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx +++ b/frontend/src/features/map_planning/layers/drawing/DrawingAttributeEditForm.tsx @@ -9,6 +9,7 @@ import SimpleButton, { ButtonVariant } from '@/components/Button/SimpleButton'; import { DebouncedSimpleFormInput } from '@/components/Form/DebouncedSimpleFormInput'; import EditIcon from '@/svg/icons/edit.svg?react'; import EraserIcon from '@/svg/icons/eraser.svg?react'; +import PencilPlusIcon from '@/svg/icons/pencil-plus.svg?react'; import useMapStore from '../../store/MapStore'; import { DrawingLayerStatusPanelContent } from './DrawingLayerStatusPanelContent'; @@ -303,6 +304,22 @@ export function DrawingAttributeEditForm({ + { + drawingLayerSetEditMode(drawingId, 'add'); + setStatusPanelContent( + , + ); + }} + title={t('drawings:bezier_polygon_add_points_between_tooltip')} + > + + + = useCallback( + (e) => { + // only unselect if we are clicking on the background + if (e.target instanceof Konva.Shape) { + return; + } + + if (editMode) return; + + useMapStore.getState().selectDrawings([]); + }, + [editMode], + ); + const handleSelectDrawing: KonvaEventListener = useCallback(() => { const selectedDrawings = (foundDrawings: DrawingDto[], konvaNode: Konva.Node) => { const drawingNode = konvaNode.getAttr('object'); @@ -654,6 +669,8 @@ 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.selectDrawing', handleUnselectDrawing); + transformerActions.addEventListener('dragend.drawings', handleMoveDrawing); transformerActions.addEventListener('transformend.drawings', handleTransformDrawing); @@ -662,6 +679,8 @@ 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.selectDrawing'); + transformerActions.removeEventListener('dragend.drawings'); transformerActions.removeEventListener('transformend.drawings'); }; @@ -672,15 +691,16 @@ function DrawingLayer(props: DrawingLayerProps) { handleMoveDrawing, handleSelectDrawing, handleTransformDrawing, + handleUnselectDrawing, props.listening, ]); - const updateNewBezierLinePoints = (points: number[][]) => { + const updateNewBezierLinePoints = (points: PolygonPoint[]) => { if (!newBezierLine) return; setNewBezierLine({ ...newBezierLine, - points: points, + points: points.map((p) => [p.x, p.y]), }); }; @@ -689,26 +709,37 @@ 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} - fillEnabled={bezierLine.fillEnabled} - x={bezierLine.x} - y={bezierLine.y} - scaleX={bezierLine.scaleX} - scaleY={bezierLine.scaleY} - onDragStart={moveToTop} - > - ))} + {bezierLines.map((bezierLine, i) => { + return ( + + handleBezierPointsChanged( + bezierLine.id, + p.map((p) => [p.x, p.y]), + ) + } + initialPoints={bezierLine.properties.points.map((p) => { + return { + x: p[0], + y: p[1], + }; + })} + strokeWidth={bezierLine.strokeWidth} + onLineClick={handleShapeClicked} + color={bezierLine.color} + x={bezierLine.x} + y={bezierLine.y} + scaleX={bezierLine.scaleX} + scaleY={bezierLine.scaleY} + onDragStart={moveToTop} + > + ); + })} {newBezierLine && ( { + return { + x: p[0], + y: p[1], + }; + })} editMode={selectedShape == DrawingShapeType.BezierPolygon ? 'draw' : undefined} strokeWidth={selectedStrokeWidth} color={selectedColor} diff --git a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx index eb23d87bc..38bb79cb6 100644 --- a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx +++ b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx @@ -8,13 +8,18 @@ import { Circle, Line } from 'react-konva'; import { DrawingDto } from '@/api_types/definitions'; import useMapStore from '@/features/map_planning/store/MapStore'; import { DrawingLayerEditMode } from '@/features/map_planning/store/MapStoreTypes'; +import { PolygonPoint } from '@/features/map_planning/types/PolygonTypes'; +import { + flattenRing, + insertPointIntoLineSegmentWithLeastDistance, +} from '@/features/map_planning/utils/PolygonUtils'; export type BezierPolygonProps = { transformerRef?: React.MutableRefObject; id: string; editMode?: DrawingLayerEditMode; - initialPoints: number[][]; - onPointsChanged: (points: number[][]) => void; + initialPoints: PolygonPoint[]; + onPointsChanged: (points: PolygonPoint[]) => void; onFinishLine?: () => void; onLineClick?: (evt: Konva.KonvaEventObject) => void; onDragStart?: (evt: Konva.KonvaEventObject) => void; @@ -29,16 +34,14 @@ export type BezierPolygonProps = { fillEnabled?: boolean; }; -type Point = number[]; +function getBetweenPoint(p1: PolygonPoint, p2: PolygonPoint, p: number) { + const x = p1.x + (p2.x - p1.x) * p; + const y = p1.y + (p2.y - p1.y) * p; -function getBetweenPoint(p1: Point, p2: Point, p: number) { - const x = p1[0] + (p2[0] - p1[0]) * p; - const y = p1[1] + (p2[1] - p1[1]) * p; - - return [x, y]; + return { x, y } as PolygonPoint; } -function getMidPoints(p1: Point, p2: Point) { +function getMidPoints(p1: PolygonPoint, p2: PolygonPoint) { return [getBetweenPoint(p1, p2, 0.3333), getBetweenPoint(p1, p2, 0.6666)]; } @@ -60,20 +63,22 @@ function BezierPolygon({ rotation, fillEnabled, }: BezierPolygonProps) { - const [points, setPoints] = useState(initialPoints); + const [points, setPoints] = useState(initialPoints); const [, setActivePoint] = useState(-1); const [activeSegments, setActiveSegments] = useState([]); - const [, setSegPos] = useState([]); + const [, setSegPos] = useState(); const mapScale = useMapStore.getState().stageRef.current?.scale(); const editModeActive = editMode != undefined; const drawingModeActive = editMode === 'draw'; const removeModeActive = editMode === 'remove'; + const addModeActive = editMode === 'add'; const lineRef = useRef(null); useEffect(() => { + console.log('initialPoints', initialPoints); setPoints(initialPoints); }, [initialPoints]); @@ -93,7 +98,11 @@ function BezierPolygon({ const pos = stage.getRelativePointerPosition(); if (pos == null) return []; - const newPoint = [pos.x - x, pos.y - y]; + const newPoint = { + x: pos.x - x, + y: pos.y - y, + }; + if (!points.length) { onPointsChanged([newPoint]); return; @@ -118,17 +127,54 @@ function BezierPolygon({ [onFinishLine], ); - const handleClick = useCallback( + const addPointBetween = useCallback( (e: Konva.KonvaEventObject) => { - if (!drawingModeActive) return; + const stage = e.target.getStage(); + if (stage == null) return []; - if (e.evt.button == 2) { - handleRightClick(e); - } else if (e.evt.button === 0) { - handleAddPoint(e); + const pos = stage.getRelativePointerPosition(); + if (pos == null) return []; + + const polygonPointsWithoutControlPoints = points.filter((_, i) => i % 3 === 0); + const { geometry, insertedAfterIndex } = insertPointIntoLineSegmentWithLeastDistance( + { rings: [polygonPointsWithoutControlPoints] }, + { x: pos.x - x, y: pos.y - y }, + 0, + 0, + false, + ); + const newPoint = geometry.rings[0][insertedAfterIndex + 1]; + + //get indexes of bounding points in the original points array + const indexOfFirstPoint = insertedAfterIndex * 3; + const indexOfSecondPoint = insertedAfterIndex * 3 + 3; + + const newPoints = [ + ...points.slice(0, indexOfFirstPoint + 1), + ...getMidPoints(polygonPointsWithoutControlPoints[insertedAfterIndex], newPoint), + newPoint, + ...getMidPoints(newPoint, polygonPointsWithoutControlPoints[insertedAfterIndex + 1]), + ...points.slice(indexOfSecondPoint), + ]; + + onPointsChanged(newPoints); + }, + [onPointsChanged, points, x, y], + ); + + const handleClick = useCallback( + (e: Konva.KonvaEventObject) => { + if (addModeActive) { + addPointBetween(e); + } else if (drawingModeActive) { + if (e.evt.button == 2) { + handleRightClick(e); + } else if (e.evt.button === 0) { + handleAddPoint(e); + } } }, - [drawingModeActive, handleAddPoint, handleRightClick], + [addModeActive, addPointBetween, drawingModeActive, handleAddPoint, handleRightClick], ); const handleDoubleClick = useCallback( @@ -172,12 +218,17 @@ function BezierPolygon({ const handlePointMove = (pointIndex: number, target: Stage | Shape) => { setPoints((points) => { const newPoints = [...points]; - newPoints[pointIndex] = [(target.attrs.x - x) / scaleX, (target.attrs.y - y) / scaleY]; + newPoints[pointIndex] = { + x: (target.attrs.x - x) / scaleX, + y: (target.attrs.y - y) / scaleY, + }; return newPoints; }); }; - const handlePointMouseUp = () => { + const handlePointMouseUp = (e: Konva.KonvaEventObject) => { + //if click was double click then do not add point + if (e.evt.detail === 2) return; onPointsChanged(points); }; @@ -192,7 +243,7 @@ function BezierPolygon({ const pos = stage.getRelativePointerPosition(); if (pos == null) return; - setSegPos([pos.x, pos.y]); + setSegPos({ x: pos.x, y: pos.y }); }; const handleLineClick = (e: Konva.KonvaEventObject, pointIndex: number) => { @@ -209,7 +260,7 @@ function BezierPolygon({ const pos = stage.getRelativePointerPosition(); if (pos == null) return []; - const newPoint = [pos.x - x, pos.y - y]; + const newPoint = { x: pos.x - x, y: pos.y - y }; const spliceIndex = pointIndex * 3 + 2; newPoints.splice( @@ -226,7 +277,7 @@ function BezierPolygon({ const handleLineMouseLeave = () => { setActiveSegments([]); - setSegPos([]); + setSegPos(undefined); }; const removePoint = (i: number) => { @@ -240,7 +291,9 @@ function BezierPolygon({ return newPoints; }); - flattenLine(i - 1); + if (i != points.length - 1) { + flattenLine(i - 1); + } }; const flattenLine = (i: number) => { @@ -253,13 +306,13 @@ function BezierPolygon({ i = newIndex; newPoints.splice(i, 0, ...getMidPoints(newPoints[i - 1], newPoints[i])); - + onPointsChanged(newPoints); return newPoints; }); }; useEffect(() => { - if (!drawingModeActive) { + if (!drawingModeActive && !addModeActive) { return; } @@ -272,7 +325,7 @@ function BezierPolygon({ useMapStore.getState().stageRef.current?.off('click.addPoint'); useMapStore.getState().stageRef.current?.off('contextmenu'); }; - }, [drawingModeActive, handleClick, handleDoubleClick, handleRightClick]); + }, [addModeActive, drawingModeActive, handleClick, handleDoubleClick, handleRightClick]); return ( <> @@ -281,7 +334,7 @@ function BezierPolygon({ 0 ? flattenRing(points) : []} stroke={color} strokeWidth={strokeWidth} hitStrokeWidth={(strokeWidth ? 1 : 0) + 100} @@ -294,6 +347,7 @@ function BezierPolygon({ scaleX={scaleX} scaleY={scaleY} rotation={rotation} + fillPatternRepeat="repeat" draggable fill={color} fillEnabled={fillEnabled} @@ -306,7 +360,7 @@ function BezierPolygon({ segments.map((segment, i) => { const isActive = activeSegments.some((segI) => i === segI); - const flatSegmentPoints = segment.flat(); + const flatSegmentPoints = flattenRing(segment); return ( @@ -355,8 +409,8 @@ function BezierPolygon({ return ( { - handlePointMouseUp(); + onMouseUp={(e) => { + handlePointMouseUp(e); }} onDragMove={({ target }) => { handlePointMove(i, target); @@ -388,7 +442,7 @@ function BezierPolygon({ {...(i % 3 ? { stroke: '#ccc', - visible: drawingModeActive, + visible: drawingModeActive || addModeActive, } : { fill: i == points.length - 1 && drawingModeActive ? '#0000ff' : '#ee90aa', diff --git a/frontend/src/features/map_planning/types/PolygonTypes.ts b/frontend/src/features/map_planning/types/PolygonTypes.ts index 9b011c6a8..4edcf8534 100644 --- a/frontend/src/features/map_planning/types/PolygonTypes.ts +++ b/frontend/src/features/map_planning/types/PolygonTypes.ts @@ -9,7 +9,7 @@ export type PolygonGeometry = { /** * ID of the coordinate type used by the backend. */ - srid: string; + srid?: string; /** * Coordinate data used to construct the polygon. */ @@ -39,7 +39,7 @@ export type PolygonPoint = { /** * ID of the coordinate type used by the backend. */ - srid: number; + srid?: number; }; export const DEFAULT_SRID = 4326; diff --git a/frontend/src/features/map_planning/utils/PolygonUtils.ts b/frontend/src/features/map_planning/utils/PolygonUtils.ts index 659d9aed8..528333e43 100644 --- a/frontend/src/features/map_planning/utils/PolygonUtils.ts +++ b/frontend/src/features/map_planning/utils/PolygonUtils.ts @@ -21,59 +21,67 @@ export function insertPointIntoLineSegmentWithLeastDistance( pointToInsert: PolygonPoint, minCoordinateDelta: number, edgeRing?: number, -): PolygonGeometry { + isClosed = true, +): { geometry: PolygonGeometry; insertedAfterIndex: number } { const newGeometry: PolygonGeometry = deepCopyGeometry(geometry); let smallestTotalDistanceToLine = Infinity; let insertNewPointAfterIndex = -1; - geometry.rings[edgeRing ?? 0] - // the last point is identical to the first point - .slice(0, geometry.rings[edgeRing ?? 0].length - 1) - .forEach((value, index, array) => { - const firstPoint = value; - const secondPoint = array[(index + 1) % array.length]; - - // Due to winding order we don't know how the first and second point are situated relative to each other. - const leftPoint = firstPoint.x < secondPoint.x ? firstPoint : secondPoint; - const rightPoint = firstPoint.x > secondPoint.x ? firstPoint : secondPoint; - const lowerPoint = firstPoint.y < secondPoint.y ? firstPoint : secondPoint; - const upperPoint = firstPoint.y > secondPoint.y ? firstPoint : secondPoint; - - const newPointBetweenFirstAndSecondPointX = - leftPoint.x < pointToInsert.x && - rightPoint.x > pointToInsert.x && - Math.abs(rightPoint.x - leftPoint.x) > minCoordinateDelta; - const newPointBetweenFirstAndSecondPointY = - lowerPoint.y < pointToInsert.y && - upperPoint.y > pointToInsert.y && - Math.abs(rightPoint.y - leftPoint.y) > minCoordinateDelta; - - // The distance calculation below would no longer be accurate because the formula was designed for lines of - // infinite length that go through points instead of fixed length line segments. - if (!newPointBetweenFirstAndSecondPointX && !newPointBetweenFirstAndSecondPointY) return; - - // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line - const distanceToLine = - Math.abs( - (secondPoint.x - firstPoint.x) * (firstPoint.y - pointToInsert.y) - - (firstPoint.x - pointToInsert.x) * (secondPoint.y - firstPoint.y), - ) / - Math.sqrt( - (secondPoint.x - firstPoint.x) * (secondPoint.x - firstPoint.x) + - (secondPoint.y - firstPoint.y) * (secondPoint.y - firstPoint.y), - ); - - if (distanceToLine < smallestTotalDistanceToLine) { - smallestTotalDistanceToLine = distanceToLine; - insertNewPointAfterIndex = index; - } - }); + const rings = newGeometry.rings[edgeRing ?? 0].slice(0, isClosed ? -1 : undefined); + + rings.forEach((value, index, array) => { + const firstPoint = value; + const secondPoint = array[(index + 1) % array.length]; + + // We don't want to insert a point between the first and last point of an unclosed ring. + if (!isClosed && index == array.length - 1) return; + + // Due to winding order we don't know how the first and second point are situated relative to each other. + const leftPoint = firstPoint.x < secondPoint.x ? firstPoint : secondPoint; + const rightPoint = firstPoint.x > secondPoint.x ? firstPoint : secondPoint; + const lowerPoint = firstPoint.y < secondPoint.y ? firstPoint : secondPoint; + const upperPoint = firstPoint.y > secondPoint.y ? firstPoint : secondPoint; + + const newPointBetweenFirstAndSecondPointX = + leftPoint.x < pointToInsert.x && + rightPoint.x > pointToInsert.x && + Math.abs(rightPoint.x - leftPoint.x) > minCoordinateDelta; + const newPointBetweenFirstAndSecondPointY = + lowerPoint.y < pointToInsert.y && + upperPoint.y > pointToInsert.y && + Math.abs(rightPoint.y - leftPoint.y) > minCoordinateDelta; + + // The distance calculation below would no longer be accurate because the formula was designed for lines of + // infinite length that go through points instead of fixed length line segments. + if (!newPointBetweenFirstAndSecondPointX && !newPointBetweenFirstAndSecondPointY) return; + + // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + const distanceToLine = + Math.abs( + (secondPoint.x - firstPoint.x) * (firstPoint.y - pointToInsert.y) - + (firstPoint.x - pointToInsert.x) * (secondPoint.y - firstPoint.y), + ) / + Math.sqrt( + (secondPoint.x - firstPoint.x) * (secondPoint.x - firstPoint.x) + + (secondPoint.y - firstPoint.y) * (secondPoint.y - firstPoint.y), + ); + + if (distanceToLine < smallestTotalDistanceToLine) { + smallestTotalDistanceToLine = distanceToLine; + insertNewPointAfterIndex = index; + } + }); // The algorithm above might discard all line segments in some cases. // If this is the case we use the function bellow as our "fallback-algorithm" if (insertNewPointAfterIndex == -1) - return insertBetweenPointsWithLeastTotalDistance(newGeometry, pointToInsert, edgeRing); + return insertBetweenPointsWithLeastTotalDistance( + newGeometry, + pointToInsert, + edgeRing, + isClosed, + ); const ring = newGeometry.rings[edgeRing ?? 0]; newGeometry.rings[edgeRing ?? 0] = ring @@ -81,7 +89,7 @@ export function insertPointIntoLineSegmentWithLeastDistance( .concat([pointToInsert]) .concat(ring.slice(insertNewPointAfterIndex + 1, ring.length)); - return newGeometry; + return { geometry: newGeometry, insertedAfterIndex: insertNewPointAfterIndex }; } /** @@ -96,28 +104,28 @@ export function insertBetweenPointsWithLeastTotalDistance( geometry: PolygonGeometry, pointToInsert: PolygonPoint, edgeRing?: number, -): PolygonGeometry { + isClosed = true, +): { geometry: PolygonGeometry; insertedAfterIndex: number } { const newGeometry: PolygonGeometry = deepCopyGeometry(geometry); let smallestTotalDistance = Infinity; let insertNewPointAfterIndex = -1; - newGeometry.rings[edgeRing ?? 0] - // the last point is identical to the first point - .slice(0, newGeometry.rings[edgeRing ?? 0].length - 1) - .forEach((value, index, array) => { - const firstPoint = value; - const secondPoint = array[(index + 1) % array.length]; + const rings = newGeometry.rings[edgeRing ?? 0].slice(0, isClosed ? -1 : undefined); - const distanceOne = calculateDistance(pointToInsert, firstPoint); - const distanceTwo = calculateDistance(pointToInsert, secondPoint); + rings.forEach((value, index, array) => { + const firstPoint = value; + const secondPoint = array[(index + 1) % array.length]; - const totalDistance = distanceOne + distanceTwo; - if (totalDistance < smallestTotalDistance) { - smallestTotalDistance = totalDistance; - insertNewPointAfterIndex = index; - } - }); + const distanceOne = calculateDistance(pointToInsert, firstPoint); + const distanceTwo = calculateDistance(pointToInsert, secondPoint); + + const totalDistance = distanceOne + distanceTwo; + if (totalDistance < smallestTotalDistance) { + smallestTotalDistance = totalDistance; + insertNewPointAfterIndex = index; + } + }); const ring = newGeometry.rings[edgeRing ?? 0]; newGeometry.rings[edgeRing ?? 0] = ring @@ -125,7 +133,7 @@ export function insertBetweenPointsWithLeastTotalDistance( .concat([pointToInsert]) .concat(ring.slice(insertNewPointAfterIndex + 1, ring.length)); - return newGeometry; + return { geometry: newGeometry, insertedAfterIndex: insertNewPointAfterIndex }; } /** From 9b122f82b79300cb668a56415bf5efb016261a08 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 12:19:08 +0000 Subject: [PATCH 15/27] add changelog entry --- doc/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.md b/doc/changelog.md index fabb8de44..d22153fba 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -10,7 +10,7 @@ Syntax: `- short text describing the change _(Your Name)_` - _()_ - _()_ -- _()_ +- Add points in middle of bezier polygon _(Daniel Steinkogler)_ - _()_ - _()_ - _()_ From 9e5f47b961d4ae168dc8ef0d885e39ab1421a872 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 12:33:00 +0000 Subject: [PATCH 16/27] feat(#1289): fix test --- frontend/src/features/map_planning/utils/PolygonUtils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/map_planning/utils/PolygonUtils.test.ts b/frontend/src/features/map_planning/utils/PolygonUtils.test.ts index a15e36a75..86f1a2c73 100644 --- a/frontend/src/features/map_planning/utils/PolygonUtils.test.ts +++ b/frontend/src/features/map_planning/utils/PolygonUtils.test.ts @@ -217,7 +217,7 @@ describe('Add a point between the two nearest points', () => { srid: '', }; - expect(insertPointIntoLineSegmentWithLeastDistance(polygon, newPoint, 0)).toEqual({ + expect(insertPointIntoLineSegmentWithLeastDistance(polygon, newPoint, 0).geometry).toEqual({ rings: [ [ { x: 0, y: 0, srid: 0 }, From eb372d171da776a4a6d00751924c399b9269b454 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 13:24:36 +0000 Subject: [PATCH 17/27] fix add point an scaled polygon --- .../map_planning/layers/drawing/shapes/BezierPolygon.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx index 38bb79cb6..493338c5a 100644 --- a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx +++ b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx @@ -78,7 +78,6 @@ function BezierPolygon({ const lineRef = useRef(null); useEffect(() => { - console.log('initialPoints', initialPoints); setPoints(initialPoints); }, [initialPoints]); @@ -99,8 +98,8 @@ function BezierPolygon({ if (pos == null) return []; const newPoint = { - x: pos.x - x, - y: pos.y - y, + x: (pos.x - x) / scaleX, + y: (pos.y - y) / scaleY, }; if (!points.length) { @@ -113,7 +112,7 @@ function BezierPolygon({ onPointsChanged(newPoints); }, - [onPointsChanged, points, x, y], + [onPointsChanged, points, scaleX, scaleY, x, y], ); const handleRightClick = useCallback( From 92aa5177ae231ba31a9905ce876b1ee579583bd1 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 14:34:11 +0000 Subject: [PATCH 18/27] feat(#1289): fix add points to scaled polygon --- .../layers/drawing/shapes/BezierPolygon.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx index 493338c5a..a2b6f8afe 100644 --- a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx +++ b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx @@ -135,15 +135,26 @@ function BezierPolygon({ if (pos == null) return []; const polygonPointsWithoutControlPoints = points.filter((_, i) => i % 3 === 0); + + const scaledPolygon = polygonPointsWithoutControlPoints.map((point) => ({ + x: point.x * scaleX, + y: point.y * scaleY, + })); + const { geometry, insertedAfterIndex } = insertPointIntoLineSegmentWithLeastDistance( - { rings: [polygonPointsWithoutControlPoints] }, + { rings: [scaledPolygon] }, { x: pos.x - x, y: pos.y - y }, 0, 0, false, ); + const newPoint = geometry.rings[0][insertedAfterIndex + 1]; + //scale back to original coordinates + newPoint.x /= scaleX; + newPoint.y /= scaleY; + //get indexes of bounding points in the original points array const indexOfFirstPoint = insertedAfterIndex * 3; const indexOfSecondPoint = insertedAfterIndex * 3 + 3; @@ -158,7 +169,7 @@ function BezierPolygon({ onPointsChanged(newPoints); }, - [onPointsChanged, points, x, y], + [onPointsChanged, points, scaleX, scaleY, x, y], ); const handleClick = useCallback( @@ -354,6 +365,7 @@ function BezierPolygon({ onDragStart={onDragStart} /> )} + {/* Segmented dashed control line AND segmented curve */} {editModeActive && segments.map((segment, i) => { From 2f8b05266461a9517c6c929570f90022db66b629 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 22:09:41 +0000 Subject: [PATCH 19/27] feat(#1289): fix rotation of bezier polygons --- .../layers/drawing/DrawingLayer.tsx | 1 + .../layers/drawing/shapes/BezierPolygon.tsx | 51 +++++++++++-------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/frontend/src/features/map_planning/layers/drawing/DrawingLayer.tsx b/frontend/src/features/map_planning/layers/drawing/DrawingLayer.tsx index ec5970ef1..d902f05a4 100644 --- a/frontend/src/features/map_planning/layers/drawing/DrawingLayer.tsx +++ b/frontend/src/features/map_planning/layers/drawing/DrawingLayer.tsx @@ -736,6 +736,7 @@ function DrawingLayer(props: DrawingLayerProps) { y={bezierLine.y} scaleX={bezierLine.scaleX} scaleY={bezierLine.scaleY} + rotation={bezierLine.rotation} onDragStart={moveToTop} > ); diff --git a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx index a2b6f8afe..433bcc4f1 100644 --- a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx +++ b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx @@ -68,6 +68,8 @@ function BezierPolygon({ const [activeSegments, setActiveSegments] = useState([]); const [, setSegPos] = useState(); + console.log('points', points); + const mapScale = useMapStore.getState().stageRef.current?.scale(); const editModeActive = editMode != undefined; @@ -97,9 +99,9 @@ function BezierPolygon({ const pos = stage.getRelativePointerPosition(); if (pos == null) return []; - const newPoint = { - x: (pos.x - x) / scaleX, - y: (pos.y - y) / scaleY, + const newPoint = lineRef.current?.getTransform().invert().point({ x: pos.x, y: pos.y }) || { + x: pos.x - x, + y: pos.y - y, }; if (!points.length) { @@ -112,7 +114,7 @@ function BezierPolygon({ onPointsChanged(newPoints); }, - [onPointsChanged, points, scaleX, scaleY, x, y], + [onPointsChanged, points, x, y], ); const handleRightClick = useCallback( @@ -136,24 +138,22 @@ function BezierPolygon({ const polygonPointsWithoutControlPoints = points.filter((_, i) => i % 3 === 0); - const scaledPolygon = polygonPointsWithoutControlPoints.map((point) => ({ - x: point.x * scaleX, - y: point.y * scaleY, - })); + const transform = lineRef.current?.getTransform().copy(); + const scaledPolygon = polygonPointsWithoutControlPoints.map( + (point) => transform?.point(point) || point, + ); const { geometry, insertedAfterIndex } = insertPointIntoLineSegmentWithLeastDistance( { rings: [scaledPolygon] }, - { x: pos.x - x, y: pos.y - y }, + { x: pos.x, y: pos.y }, 0, 0, false, ); - const newPoint = geometry.rings[0][insertedAfterIndex + 1]; - - //scale back to original coordinates - newPoint.x /= scaleX; - newPoint.y /= scaleY; + //scale newPoint to original coordinates + let newPoint = geometry.rings[0][insertedAfterIndex + 1]; + newPoint = transform?.copy().invert().point(newPoint) || newPoint; //get indexes of bounding points in the original points array const indexOfFirstPoint = insertedAfterIndex * 3; @@ -169,7 +169,7 @@ function BezierPolygon({ onPointsChanged(newPoints); }, - [onPointsChanged, points, scaleX, scaleY, x, y], + [onPointsChanged, points], ); const handleClick = useCallback( @@ -228,10 +228,16 @@ function BezierPolygon({ const handlePointMove = (pointIndex: number, target: Stage | Shape) => { setPoints((points) => { const newPoints = [...points]; - newPoints[pointIndex] = { - x: (target.attrs.x - x) / scaleX, - y: (target.attrs.y - y) / scaleY, + + newPoints[pointIndex] = lineRef.current?.getTransform().copy().invert().point({ + x: target.x(), + y: target.y(), + }) || { + x: target.x(), + y: target.y(), }; + + console.log('newPoints', newPoints); return newPoints; }); }; @@ -239,6 +245,7 @@ function BezierPolygon({ const handlePointMouseUp = (e: Konva.KonvaEventObject) => { //if click was double click then do not add point if (e.evt.detail === 2) return; + onPointsChanged(points); }; @@ -388,6 +395,7 @@ function BezierPolygon({ scaleY={scaleY} x={x} y={y} + rotation={rotation} active={editModeActive} onMouseMove={(e) => handleLineMouserOver(e, i)} onMouseLeave={handleLineMouseLeave} @@ -404,6 +412,7 @@ function BezierPolygon({ y={y} scaleX={scaleX} scaleY={scaleY} + rotation={rotation} points={flatSegmentPoints} stroke={'#bbb'} strokeWidth={2} @@ -417,11 +426,13 @@ function BezierPolygon({ {editModeActive && points.map((p, i) => { + const transformedPoint = + lineRef.current?.getTransform().copy().point({ x: p.x, y: p.y }) || p; return ( Date: Tue, 16 Apr 2024 22:14:24 +0000 Subject: [PATCH 20/27] feat(#1289): remove console logs --- .devcontainer/.env | 2 +- backend/.env.sample | 2 +- .../map_planning/layers/drawing/shapes/BezierPolygon.tsx | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.devcontainer/.env b/.devcontainer/.env index 5b08e5348..0af792c03 100644 --- a/.devcontainer/.env +++ b/.devcontainer/.env @@ -8,5 +8,5 @@ BIND_ADDRESS_PORT=8080 VITE_BASE_API_URL=http://localhost:8080 VITE_NEXTCLOUD_URI=https://cloud.permaplant.net # OAuth2 (other URLs will be fetched from this URL) -AUTH_DISCOVERY_URI=https://auth.permaplant.net/realms/PermaplanT/.well-known/openid-configuration +AUTH_DISCOVERY_URI=http://host.docker.internal:8081/realms/PermaplanT/.well-known/openid-configuration AUTH_CLIENT_ID=localhost diff --git a/backend/.env.sample b/backend/.env.sample index bf8386cd4..a1f5db756 100644 --- a/backend/.env.sample +++ b/backend/.env.sample @@ -4,7 +4,7 @@ BIND_ADDRESS_HOST=127.0.0.1 BIND_ADDRESS_PORT=8080 # OAuth2 (other URLs will be fetched from this URL) -AUTH_DISCOVERY_URI=https://auth.permaplant.net/realms/PermaplanT/.well-known/openid-configuration +AUTH_DISCOVERY_URI=https://host.docker.internal:8081/realms/PermaplanT/.well-known/openid-configuration AUTH_CLIENT_ID=localhost # Logging config (will be used by env_logger) diff --git a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx index 433bcc4f1..a53570c26 100644 --- a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx +++ b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx @@ -68,8 +68,6 @@ function BezierPolygon({ const [activeSegments, setActiveSegments] = useState([]); const [, setSegPos] = useState(); - console.log('points', points); - const mapScale = useMapStore.getState().stageRef.current?.scale(); const editModeActive = editMode != undefined; @@ -237,7 +235,6 @@ function BezierPolygon({ y: target.y(), }; - console.log('newPoints', newPoints); return newPoints; }); }; From fb693bcb4d3630afcbda32b5895df372890e2a19 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Tue, 16 Apr 2024 22:15:50 +0000 Subject: [PATCH 21/27] revert docker setup changes --- .devcontainer/.env | 2 +- backend/.env.sample | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/.env b/.devcontainer/.env index 0af792c03..5b08e5348 100644 --- a/.devcontainer/.env +++ b/.devcontainer/.env @@ -8,5 +8,5 @@ BIND_ADDRESS_PORT=8080 VITE_BASE_API_URL=http://localhost:8080 VITE_NEXTCLOUD_URI=https://cloud.permaplant.net # OAuth2 (other URLs will be fetched from this URL) -AUTH_DISCOVERY_URI=http://host.docker.internal:8081/realms/PermaplanT/.well-known/openid-configuration +AUTH_DISCOVERY_URI=https://auth.permaplant.net/realms/PermaplanT/.well-known/openid-configuration AUTH_CLIENT_ID=localhost diff --git a/backend/.env.sample b/backend/.env.sample index a1f5db756..bf8386cd4 100644 --- a/backend/.env.sample +++ b/backend/.env.sample @@ -4,7 +4,7 @@ BIND_ADDRESS_HOST=127.0.0.1 BIND_ADDRESS_PORT=8080 # OAuth2 (other URLs will be fetched from this URL) -AUTH_DISCOVERY_URI=https://host.docker.internal:8081/realms/PermaplanT/.well-known/openid-configuration +AUTH_DISCOVERY_URI=https://auth.permaplant.net/realms/PermaplanT/.well-known/openid-configuration AUTH_CLIENT_ID=localhost # Logging config (will be used by env_logger) From 12000239323b71a07a30939a54bda6d7e7f6c019 Mon Sep 17 00:00:00 2001 From: Daniel Steinkogler Date: Wed, 17 Apr 2024 12:15:23 +0000 Subject: [PATCH 22/27] feat(#1289): fix deletion of points on rotated polygons --- .../layers/drawing/shapes/BezierPolygon.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx index a53570c26..4be50904f 100644 --- a/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx +++ b/frontend/src/features/map_planning/layers/drawing/shapes/BezierPolygon.tsx @@ -76,6 +76,11 @@ function BezierPolygon({ const addModeActive = editMode === 'add'; const lineRef = useRef(null); + const lineTransform = useRef(null); + + if (lineRef.current) { + lineTransform.current = lineRef.current?.getTransform().copy(); + } useEffect(() => { setPoints(initialPoints); @@ -97,7 +102,7 @@ function BezierPolygon({ const pos = stage.getRelativePointerPosition(); if (pos == null) return []; - const newPoint = lineRef.current?.getTransform().invert().point({ x: pos.x, y: pos.y }) || { + const newPoint = lineTransform.current?.copy().invert().point({ x: pos.x, y: pos.y }) || { x: pos.x - x, y: pos.y - y, }; @@ -136,7 +141,7 @@ function BezierPolygon({ const polygonPointsWithoutControlPoints = points.filter((_, i) => i % 3 === 0); - const transform = lineRef.current?.getTransform().copy(); + const transform = lineTransform.current?.copy(); const scaledPolygon = polygonPointsWithoutControlPoints.map( (point) => transform?.point(point) || point, ); @@ -227,7 +232,7 @@ function BezierPolygon({ setPoints((points) => { const newPoints = [...points]; - newPoints[pointIndex] = lineRef.current?.getTransform().copy().invert().point({ + newPoints[pointIndex] = lineTransform.current?.copy().invert().point({ x: target.x(), y: target.y(), }) || { @@ -382,6 +387,7 @@ function BezierPolygon({ {/* Curve */} { - const transformedPoint = - lineRef.current?.getTransform().copy().point({ x: p.x, y: p.y }) || p; + const transformedPoint = lineTransform.current?.point({ x: p.x, y: p.y }) || p; return ( Date: Wed, 17 Apr 2024 17:36:56 +0200 Subject: [PATCH 23/27] test(#1271): Add warn signs to attributeEditForm for multi-selected plant-areas --- doc/changelog.md | 2 +- .../components/PlantLayerLeftToolbar.tsx | 4 +- .../components/PlantingAttributeEditForm.tsx | 57 ++++++++++++------- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/doc/changelog.md b/doc/changelog.md index fabb8de44..c33d8419b 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -17,7 +17,7 @@ Syntax: `- short text describing the change _(Your Name)_` - _()_ - _()_ - _()_ -- _()_ +- Add warn signs to sizes for multi-selected plant-areas _(Lukas Anton Lakits)_ - _()_ - _()_ - _()_ diff --git a/frontend/src/features/map_planning/layers/plant/components/PlantLayerLeftToolbar.tsx b/frontend/src/features/map_planning/layers/plant/components/PlantLayerLeftToolbar.tsx index 3f28a6a63..44d185fab 100644 --- a/frontend/src/features/map_planning/layers/plant/components/PlantLayerLeftToolbar.tsx +++ b/frontend/src/features/map_planning/layers/plant/components/PlantLayerLeftToolbar.tsx @@ -80,8 +80,8 @@ export function PlantLayerLeftToolbar() { id: selectedPlanting.id, x: selectedPlanting.x, y: selectedPlanting.y, - sizeX: Math.round(sizeX), - sizeY: Math.round(sizeY), + sizeX: isNaN(sizeX) ? selectedPlanting.sizeX : Math.round(sizeX), + sizeY: isNaN(sizeY) ? selectedPlanting.sizeY : Math.round(sizeY), rotation: selectedPlanting.rotation, })); diff --git a/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx b/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx index 0c741fbc7..072050e41 100644 --- a/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx +++ b/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx @@ -23,8 +23,8 @@ const PlantingAttributeEditFormSchema = z addDate: z.nullable(z.string()).transform((value) => value || undefined), removeDate: z.nullable(z.string()).transform((value) => value || undefined), plantingNotes: z.nullable(z.string()), - sizeX: z.optional(z.number().int()), - sizeY: z.optional(z.number().int()), + sizeX: z.optional(z.number().int().or(z.nan())), + sizeY: z.optional(z.number().int().or(z.nan())), }) .refine((schema) => !schema.removeDate || !schema.addDate || schema.addDate < schema.removeDate, { path: ['dateRelation'], @@ -58,6 +58,8 @@ export type PlantingAttributeEditFormProps = EditPlantingAttributesProps & { addDateShowDifferentValueWarning?: boolean; removeDateShowDifferentValueWarning?: boolean; plantingNoteShowDifferentValueWarning?: boolean; + sizeXDifferentValueWarning?: boolean; + sizeYDifferentValueWarning?: boolean; removeDateDefaultValue: string; plantingNotesDefaultValue: string; widthDefaultValue: number | undefined; @@ -164,6 +166,12 @@ export function MultiplePlantingsAttributeForm({ plantingNoteShowDifferentValueWarning={plantings.some( (planting) => planting.plantingNotes !== getCommonPlantingNotes(), )} + sizeXDifferentValueWarning={plantings.some( + (planting) => planting.sizeX !== getCommonWidth(), + )} + sizeYDifferentValueWarning={plantings.some( + (planting) => planting.sizeY !== getCommonHeight(), + )} removeDateDefaultValue={getCommonRemoveDate() ?? ''} plantingNotesDefaultValue={getCommonPlantingNotes() ?? ''} widthDefaultValue={getCommonWidth()} @@ -183,6 +191,8 @@ function PlantingAttributeEditForm({ addDateShowDifferentValueWarning, removeDateShowDifferentValueWarning, plantingNoteShowDifferentValueWarning, + sizeXDifferentValueWarning, + sizeYDifferentValueWarning, removeDateDefaultValue, plantingNotesDefaultValue, onWidthChange, @@ -264,25 +274,30 @@ function PlantingAttributeEditForm({ {plantCountInfo.total} )} - - - +
+ + {sizeXDifferentValueWarning && } +
+
+ + {sizeYDifferentValueWarning && } +

)} From 0e88a69f5b29f28c5c61513e388731dd4e10234d Mon Sep 17 00:00:00 2001 From: Markus Raab Date: Sat, 20 Apr 2024 08:25:03 +0200 Subject: [PATCH 24/27] wait longer for DB to get ready --- ci/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile index fe4026dae..91e5e3d44 100644 --- a/ci/Jenkinsfile +++ b/ci/Jenkinsfile @@ -9,7 +9,7 @@ def node_info() { // Function duplicated in `Jenkinsfile.release` without the command parameter def wait_for_db(String command) { - retry(6) { + retry(10) { sleep(time: 5, unit: 'SECONDS') sh "${command}" } From 090a1f2fa42ba00e2a955547ad8dc0b8097e0a2f Mon Sep 17 00:00:00 2001 From: andreicristian97 <46008622+andreicristian97@users.noreply.github.com> Date: Sun, 21 Apr 2024 18:08:43 +0000 Subject: [PATCH 25/27] Applied z-index value to tooltip, added paragraph to docs about z-index usage --- doc/changelog.md | 4 ++-- doc/guidelines/frontend-ui-usability.md | 16 ++++++++++++++++ .../components/PlantingAttributeEditForm.tsx | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/doc/changelog.md b/doc/changelog.md index 08db20a38..fac54eacd 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -13,8 +13,8 @@ Syntax: `- short text describing the change _(Your Name)_` - _()_ - _()_ - _()_ -- _()_ -- _()_ +- Fixed warning text from left toolbar being cut by map elements. _(Andrei Dinu)_ +- Added paragraph about z-index usage to Frontend UI Usability Guideline in Docs _(Andrei Dinu)_ - _()_ - Add tests for hooks in frontend/layers _(Lukas Anton Lakits)_ - _()_ diff --git a/doc/guidelines/frontend-ui-usability.md b/doc/guidelines/frontend-ui-usability.md index e375d37b1..9e76d516f 100644 --- a/doc/guidelines/frontend-ui-usability.md +++ b/doc/guidelines/frontend-ui-usability.md @@ -180,3 +180,19 @@ Error messages should fulfill following criteria: E.g.: "Sorry, I **cannot communicate** with my server, there is probably some network problem or the server is down. _Please retry later._" + +## Z-Index + +For a clean and maintainable layout, z-index should be used sparingly. Isolate z-index control to the **root element** of each component that needs overlapping elements. +This creates staking contexts within components, preventing z-index conflicts across the application. +If an element within a component doesn't require special z-index positioning, leave it to the default value of **'auto'**. + +List of z-index values currently present in the application, for future reference: + +- _BottomStatusPanel_ (inner Component of BaseStage.tsx): 10 +- _Tooltip_ in _PlanningAttributeEditForm_ component: 20 +- _Leaflet_ elements from OpenStreetMap (zoom +/- buttons, citation label on bottom right): 800 (note: pre-defined value coming from the external library used) +- _Navbar_: 1001 (note: value chosen to be guaranteed "on top" of "Leaflet" elements) +- _TransparentBackground_ (usage in modals): 1010 +- _ModalContainer_ (usage in modals): 1020 +- _Toast notifications_: 9999 (note: pre-defined value coming from the external library used) diff --git a/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx b/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx index 072050e41..6c69a5575 100644 --- a/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx +++ b/frontend/src/features/map_planning/layers/plant/components/PlantingAttributeEditForm.tsx @@ -375,7 +375,7 @@ export function MultiplePlantingsDifferentValueAlert() { data-tooltip-content={t('plantings:multiple_plantings_different_value_alert')} className="mb-3 mt-auto h-5 w-5 flex-shrink-0 text-orange-400" /> - + ); } From 82cca7fe517ab5773272af2d61f0923259dabe93 Mon Sep 17 00:00:00 2001 From: Christoph Schreiner Date: Mon, 22 Apr 2024 15:36:59 +0200 Subject: [PATCH 26/27] Increase cargo net.retry configuration to 10 to handle random network errors --- doc/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Dockerfile b/doc/Dockerfile index 6418e28dc..7beb06c71 100644 --- a/doc/Dockerfile +++ b/doc/Dockerfile @@ -3,7 +3,8 @@ FROM rust:1.67.1-slim-bookworm AS builder ENV MDBOOK_VERSION=0.4.23 \ - MDBOOK_MERMAID_VERSION=0.12.6 + MDBOOK_MERMAID_VERSION=0.12.6 \ + CARGO_NET_RETRY=10 RUN apt-get update && \ apt-get install -y --no-install-recommends \ From 88b2203d8d16b882c91e8a7c4640207880bf9051 Mon Sep 17 00:00:00 2001 From: Christoph Schreiner Date: Mon, 22 Apr 2024 15:38:27 +0200 Subject: [PATCH 27/27] Add changelog entry --- doc/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.md b/doc/changelog.md index 08db20a38..e62d37761 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -37,7 +37,7 @@ Syntax: `- short text describing the change _(Your Name)_` - _()_ - _()_ - _()_ -- _()_ +- Increase cargo net.retry configuration to handle random network errors during CI. _(Christoph Schreiner)_ - _()_ - _()_ - _()_