diff --git a/assets/global.scss b/assets/global.scss index 46af7f5..a975db5 100644 --- a/assets/global.scss +++ b/assets/global.scss @@ -1,6 +1,7 @@ @import "variables"; @import "node_modules/swiper/swiper.scss"; @import "../node_modules/react-github-button/assets/style.css"; +@import "../node_modules/leaflet/dist/leaflet.css"; :root { --backgroundColor: #{$background-color}; diff --git a/assets/images/marker-icon-2x.png b/assets/images/marker-icon-2x.png new file mode 100644 index 0000000..88f9e50 Binary files /dev/null and b/assets/images/marker-icon-2x.png differ diff --git a/assets/images/marker-shadow.png b/assets/images/marker-shadow.png new file mode 100644 index 0000000..9fd2979 Binary files /dev/null and b/assets/images/marker-shadow.png differ diff --git a/components/form/admin/types/image.type.tsx b/components/form/admin/types/image.type.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/form/admin/types/index.ts b/components/form/admin/types/index.ts index 9b36664..842c1fd 100644 --- a/components/form/admin/types/index.ts +++ b/components/form/admin/types/index.ts @@ -1,33 +1,22 @@ -import React from 'react' -import { CheckboxType } from './checkbox.type' -import { DateType } from './date.type' -import { DropdownType } from './dropdown.type' -import { EmailType } from './email.type' -import { HiddenType } from './hidden.type' -import { LinkType } from './link.type' -import { NumberType } from './number.type' -import { RadioType } from './radio.type' -import { RatingType } from './rating.type' -import { SliderType } from './slider.type' -import { TextType } from './text.type' -import { TextareaType } from './textarea.type' +import dynamic from 'next/dynamic' +import { ComponentType } from 'react' import { AdminFieldTypeProps } from './type.props' -import { YesNoType } from './yes_no.type' export const adminTypes: { - [key: string]: React.FC + [key: string]: ComponentType } = { - date: DateType, - dropdown: DropdownType, - checkbox: CheckboxType, - email: EmailType, - hidden: HiddenType, - link: LinkType, - number: NumberType, - radio: RadioType, - rating: RatingType, - slider: SliderType, - textarea: TextareaType, - textfield: TextType, - yes_no: YesNoType, + checkbox: dynamic(() => import('./checkbox.type').then(c => c.CheckboxType )), + date: dynamic(() => import('./date.type').then(c => c.DateType )), + dropdown: dynamic(() => import('./dropdown.type').then(c => c.DropdownType )), + email: dynamic(() => import('./email.type').then(c => c.EmailType )), + hidden: dynamic(() => import('./hidden.type').then(c => c.HiddenType )), + link: dynamic(() => import('./link.type').then(c => c.LinkType )), + location: dynamic(() => import('./location.type').then(c => c.LocationType ), { ssr: false }), + number: dynamic(() => import('./number.type').then(c => c.NumberType )), + radio: dynamic(() => import('./radio.type').then(c => c.RadioType )), + rating: dynamic(() => import('./rating.type').then(c => c.RatingType )), + slider: dynamic(() => import('./slider.type').then(c => c.SliderType )), + textarea: dynamic(() => import('./textarea.type').then(c => c.TextareaType )), + textfield: dynamic(() => import('./text.type').then(c => c.TextType )), + yes_no: dynamic(() => import('./yes_no.type').then(c => c.YesNoType )), } diff --git a/components/form/admin/types/location.type.tsx b/components/form/admin/types/location.type.tsx new file mode 100644 index 0000000..1a6e215 --- /dev/null +++ b/components/form/admin/types/location.type.tsx @@ -0,0 +1,142 @@ +import { Alert, Form, Input, InputNumber, Space } from 'antd' +import React from 'react' +import { useTranslation } from 'react-i18next' +import { MapContainer, TileLayer } from 'react-leaflet' +import { DraggableMarker } from '../../../map/draggable.marker' +import { AdminFieldTypeProps } from './type.props' + +export const LocationType: React.FC = (props) => { + const { t } = useTranslation() + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + {(form) => { + //const prefix = React.useContext(FormItemContext).prefixName + const prefix = (form as any).prefixName + + const zoom = form.getFieldValue([ + ...prefix, + props.field.name as string, + 'optionKeys', + 'initialZoom', + ]) + + const center = form.getFieldValue([ + ...prefix, + props.field.name as string, + 'defaultValue', + ]) + + const tiles = form.getFieldValue([ + ...prefix, + props.field.name as string, + 'optionKeys', + 'tiles', + ]) + + if (!tiles) { + return + } + + return ( +
+ + + {center.lat && center.lng && ( + { + form.setFields([ + { + name: [ + ...prefix, + props.field.name as string, + 'defaultValue', + 'lng', + ], + value: next.lng, + }, + { + name: [ + ...prefix, + props.field.name as string, + 'defaultValue', + 'lat', + ], + value: next.lat, + }, + ]) + }} + /> + )} + +
+ ) + }} +
+
+ ) +} diff --git a/components/form/types/image.type.tsx b/components/form/types/image.type.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/form/types/map.type.tsx b/components/form/types/map.type.tsx new file mode 100644 index 0000000..e69de29 diff --git a/components/map/draggable.marker.tsx b/components/map/draggable.marker.tsx new file mode 100644 index 0000000..3788779 --- /dev/null +++ b/components/map/draggable.marker.tsx @@ -0,0 +1,36 @@ +import L from 'leaflet' +import React, { FC, useMemo, useRef } from 'react' +import { Marker } from 'react-leaflet' + +interface Props { + value: { lat: number, lng: number } + onChange: (value: { lat: number, lng: number }) => void +} + +export const DraggableMarker: FC = (props) => { + const markerRef = useRef(null) + const eventHandlers = useMemo( + () => ({ + dragend() { + const marker = markerRef.current + if (marker != null) { + props.onChange(marker.getLatLng()) + } + }, + }), + [], + ) + return ( + + ) +} diff --git a/locales/en/type.json b/locales/en/type.json index 006d7b5..3db45d6 100644 --- a/locales/en/type.json +++ b/locales/en/type.json @@ -1,6 +1,13 @@ { "add": "Add Field", "confirmDelete": "Really remove this field? Check that it is not referenced anywhere!", + "checkbox": { + "addOption": "Add Checkbox", + "default": "Default Value", + "name": "Checkbox", + "options": "Checkboxes", + "removeOption": "Remove" + }, "date": { "default": "Default Date", "max": "Max Date", @@ -31,6 +38,10 @@ "default": "Default Link", "name": "URL" }, + "location": { + "name": "Map Location", + "default": "Default Location" + }, "number": { "default": "Default Number", "name": "Number" diff --git a/package.json b/package.json index 559d63b..2085463 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "imagemin-optipng": "^8.0.0", "isomorphic-fetch": "^3.0.0", "jimp": "^0.16.1", + "leaflet": "^1.7.1", "next": "^12.1.0", "next-compose-plugins": "^2.2.1", "next-optimized-images": "^2.6.2", @@ -39,6 +40,7 @@ "react-i18next": "^11.15.5", "react-icons": "^4.3.1", "react-id-swiper": "^4.0.0", + "react-leaflet": "^3.2.5", "react-markdown": "^8.0.0", "react-redux": "^7.2.6", "redux": "^4.1.2", @@ -50,6 +52,7 @@ "swiper": "^8.0.6" }, "devDependencies": { + "@types/leaflet": "^1.7.9", "@types/mathjs": "^9.4.2", "@types/node": "^16.11.17", "@types/node-fetch": "^3.0.3", diff --git a/yarn.lock b/yarn.lock index 4f65765..238219f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -682,6 +682,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@react-leaflet/core@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@react-leaflet/core/-/core-1.1.1.tgz#827fd05bb542cf874116176d8ef48d5b12163f81" + integrity sha512-7PGLWa9MZ5x/cWy8EH2VzI4T8q5WpuHbixzCDXqixP/WyqwIrg5NDUPgYuFnB4IEIZF+6nA265mYzswFo/h1Pw== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -714,6 +719,11 @@ dependencies: "@types/ms" "*" +"@types/geojson@*": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + "@types/hast@^2.0.0": version "2.3.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" @@ -734,6 +744,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/leaflet@^1.7.9": + version "1.7.9" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.7.9.tgz#7993d34f14cfa88c45b3d490daba39a3a1be9a2b" + integrity sha512-H8vPgD49HKzqM41ArHGZM70g/tfhp8W+JcPxfnF+5H/Xvp+xiP+KQOUNWU8U89fqS1Jj3cpRY/+nbnaHFzwnFA== + dependencies: + "@types/geojson" "*" + "@types/mathjs@^9.4.2": version "9.4.2" resolved "https://registry.yarnpkg.com/@types/mathjs/-/mathjs-9.4.2.tgz#beec20340d768171fed8331b08fb321d218ec6e1" @@ -3515,6 +3532,11 @@ lazystream@^1.0.0: dependencies: readable-stream "^2.0.5" +leaflet@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" + integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw== + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -5131,6 +5153,13 @@ react-is@^17.0.0, react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-leaflet@^3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/react-leaflet/-/react-leaflet-3.2.5.tgz#bec0bfab9dd8c2b030ea630f7a0687a60322ca7d" + integrity sha512-Z3KZ+4SijsRbbrt2I1a3ZDY6+V6Pm91eYTdxTN18G6IOkFRsJo1BuSPLFnyFrlF3WDjQFPEcTPkEgD1VEeAoBg== + dependencies: + "@react-leaflet/core" "^1.1.1" + react-markdown@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.0.tgz#3243296a59ddb0f451d262cc2e11123674b416c2"