From ae3d1ec831cae3906c2eb6601f5055c6a75e57ff Mon Sep 17 00:00:00 2001 From: Nudesuppe42 Date: Mon, 18 Mar 2024 20:15:22 +0100 Subject: [PATCH] feat(map): :sparkles: Edit Claim Area snapping, new edit GUI --- package.json | 1 + src/pages/calendar/manage.tsx | 1 + src/pages/map/edit/index.tsx | 570 ++++++++ src/pages/me/index.tsx | 4 +- src/styles/globals.css | 8 +- .../mapbox-gl-draw-snap-mode.d.ts | 1 + yarn.lock | 1291 ++++++++++++++++- 7 files changed, 1871 insertions(+), 5 deletions(-) create mode 100644 src/pages/map/edit/index.tsx create mode 100644 typings/mapbox-gl-draw-snap-mode/mapbox-gl-draw-snap-mode.d.ts diff --git a/package.json b/package.json index 09063210..5fc8c9e9 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "keycloak-js": "^19.0.1", "mantine-contextmenu": "^7.3.3", "mapbox-gl": "^2.9.1", + "mapbox-gl-draw-snap-mode": "^0.2.0", "mapbox-gl-style-switcher": "^1.0.11", "minimatch": "^8.0.2", "next": "^14.1.0", diff --git a/src/pages/calendar/manage.tsx b/src/pages/calendar/manage.tsx index f08b73b8..112a80e4 100644 --- a/src/pages/calendar/manage.tsx +++ b/src/pages/calendar/manage.tsx @@ -112,6 +112,7 @@ const Calendar: NextPage = () => { { + const { t } = useTranslation('map'); + const router = useRouter(); + const clipboard = useClipboard(); + + const [state, setState, contextHandler] = useContextMenu({ disableEventPosition: false }); + const [clientPos, setClientPos] = useState<{ lat: number | null; lng: number | null }>({ + lat: null, + lng: null, + }); + const [map, setMap] = useState(); + const user = useUser(); + const [draw, setDraw] = useState( + new MapboxDraw({ + displayControlsDefault: false, + modes: { + ...MapboxDraw.modes, + draw_point: SnapPointMode, + draw_polygon: SnapPolygonMode, + draw_line_string: SnapLineMode, + direct_select: SnapDirectSelect, + }, + // Styling guides + styles: SnapModeDrawStyles, + userProperties: true, //@ts-ignore + snap: true, + snapOptions: { + snapPx: 15, + snapToMidPoints: true, + snapVertexPriorityDistance: 0.0025, + }, + guides: false, + }), + ); + + const [selected, setSelected] = useState(); + const [loading, setLoading] = useState(false); + const [builderSearch, setBuilderSearch] = useDebouncedState('', 1500); + const [builderSearchLoading, setBuilderSearchLoading] = useState(false); + const [builderSearchResults, setBuilderSearchResults] = useState([]); + + const isAbleToUpdate = (feature: any) => { + if (feature.properties?.owner?.id == user.user.id) return { able: true, type: 'OWNER' }; + if (user.hasPermission('team.claim.list', feature.properties.buildTeam.id)) + return { able: true, type: 'TEAM' }; + return { able: false, type: '' }; + }; + + useEffect(() => { + if (selected) { + const { able, type: ableType } = isAbleToUpdate(selected); + if (!able) { + showNotification({ + title: 'Warning', + color: 'orange', + message: 'You cannot edit this claim', + icon: , + }); + draw.changeMode('simple_select'); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selected]); + + useEffect(() => { + if (builderSearch.length > 0) { + fetch( + process.env.NEXT_PUBLIC_API_URL + + `/builders/search?search=${builderSearch}&take=5&exact=false`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + user.token, + }, + }, + ) + .then((res) => res.json()) + .then((res) => { + if (res.errors) { + showNotification({ + title: 'Search failed', + message: res.message, + color: 'red', + }); + setBuilderSearchLoading(false); + } else { + setBuilderSearchResults(res); + setBuilderSearchLoading(false); + } + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [builderSearch]); + + const handleUpdate = (arg: string, value: any) => { + if (selected) { + draw.setFeatureProperty(selected.id, arg, value); + setSelected(draw.get(selected.id)); + } + }; + + const handleDelete = () => {}; + + const handleSave = () => { + setSelected(draw.get(selected.id)); + setLoading(true); + fetch( + process.env.NEXT_PUBLIC_API_URL + `/claims/${selected.properties.id}?coordType=numberarray`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + user.token, + }, + body: JSON.stringify({ + ...selected, + area: selected.geometry.coordinates[0], + buidings: undefined, + buildTeam: undefined, + osmName: undefined, + owner: undefined, + }), + }, + ) + .then((res) => res.json()) + .then(async (res) => { + if (res.errors) { + showNotification({ + title: 'Update failed', + message: res.error, + color: 'red', + }); + setLoading(false); + } else { + showNotification({ + title: 'Claim updated', + message: 'All Data has been saved', + color: 'green', + icon: , + }); + + const geojson = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/claims/geojson?props=true`, + ).then((r) => r.json()); + + draw.set(geojson); + setLoading(false); + } + }); + }; + + return ( + + + +
+ {!selected ? ( + <> +

Edit Claims

+

Select Claim

+

+ Select a Claim on the Map to edit it. You can select your own Claims or Claims of + BuildTeams in which you have the correct Permissions. +

+

Edit Claim Area

+

+ When having a Claim selected, you can click on it again to enter edit mode. In this + mode you can drag all corners and add new ones by draging the smaller dots between + corners. All corners you add will automatically snap to other corners and claims in + the area. +

+

Edit Claim Properties

+

+ If you have selected a Claim on the Map, all its properties which you can edit will + appear here. The area and building count will be automatically calculated once you + save your edit. +

+ } mt="xl"> + Do not forget to save your changes, they will be overriden once you reload the Page. + + + ) : ( + <> +

Edit Claim

+ + {isAbleToUpdate(selected).type == 'TEAM' && ( + } + color="yellow" + mb="lg" + > + This claim does not belong to you. You are editing it on behalf of{' '} + {selected.properties.buildTeam.name}. + + )} +

Claim Properties

+ } mb="lg"> + Images can only be added and removed on the main Map as of right now. + + handleUpdate('name', e.target.value)} + mb="md" + /> + handleUpdate('city', e.target.value)} + mb="md" + /> +