diff --git a/.eslintrc.json b/.eslintrc.json
index 4dc11c0d65..8f4f68036b 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -28,5 +28,8 @@
],
"rules": {
"no-self-assign": "off"
+ },
+ "globals": {
+ "GeolocationPositionError": true
}
}
diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json
index cacd70a89b..6873ae547d 100644
--- a/packages/builder/src/components/design/AppPreview/componentStructure.json
+++ b/packages/builder/src/components/design/AppPreview/componentStructure.json
@@ -82,7 +82,8 @@
"link",
"icon",
"embed",
- "markdownviewer"
+ "markdownviewer",
+ "embeddedmap"
]
}
]
diff --git a/packages/client/manifest.json b/packages/client/manifest.json
index fab24e4aa0..be718ed271 100644
--- a/packages/client/manifest.json
+++ b/packages/client/manifest.json
@@ -2524,6 +2524,82 @@
}
]
},
+ "embeddedmap": {
+ "name": "Embedded Map",
+ "icon": "Location",
+ "styles": ["size"],
+ "editable": true,
+ "draggable": false,
+ "illegalChildren": [
+ "section"
+ ],
+ "settings": [
+ {
+ "type": "dataProvider",
+ "label": "Provider",
+ "key": "dataProvider"
+ },
+ {
+ "type": "boolean",
+ "label": "Enable Fullscreen",
+ "key": "fullScreenEnabled",
+ "defaultValue": true
+ },
+ {
+ "type": "boolean",
+ "label": "Enable Location",
+ "key": "locationEnabled",
+ "defaultValue": true
+ },
+ {
+ "type": "boolean",
+ "label": "Enable Zoom",
+ "key": "zoomEnabled",
+ "defaultValue": true
+ },
+ {
+ "type": "number",
+ "label": "Zoom Level (0-100)",
+ "key": "zoomLevel",
+ "defaultValue": 72,
+ "max" : 100,
+ "min" : 0
+ },
+ {
+ "type": "field",
+ "label": "Latitude Key",
+ "key": "latitudeKey"
+ },
+ {
+ "type": "field",
+ "label": "Longitude Key",
+ "key": "longitudeKey"
+ },
+ {
+ "type": "field",
+ "label": "Title Key",
+ "key": "titleKey"
+ },
+ {
+ "type": "text",
+ "label": "Tile URL",
+ "key": "tileURL",
+ "defaultValue": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+ },
+ {
+ "type": "text",
+ "label": "Default Location (lat,lng)",
+ "key": "defaultLocation",
+ "defaultValue": "51.5072,-0.1276"
+ },
+ {
+ "type": "text",
+ "label": "Map Attribution",
+ "key": "mapAttribution",
+ "defaultValue": "OpenStreetMap contributors"
+ }
+ ]
+ },
"attachmentfield": {
"name": "Attachment",
"icon": "Attach",
diff --git a/packages/client/package.json b/packages/client/package.json
index 8f6bdf7fa4..a6d3177d30 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -33,8 +33,11 @@
"apexcharts": "^3.22.1",
"dayjs": "^1.10.5",
"downloadjs": "1.4.7",
+ "leaflet": "^1.7.1",
"regexparam": "^1.3.0",
"rollup-plugin-polyfill-node": "^0.8.0",
+ "sanitize-html": "^2.7.0",
+ "screenfull": "^6.0.1",
"shortid": "^2.2.15",
"svelte": "^3.38.2",
"svelte-apexcharts": "^1.0.2",
diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte
index 78fff52426..e4176587ee 100644
--- a/packages/client/src/components/Component.svelte
+++ b/packages/client/src/components/Component.svelte
@@ -103,7 +103,12 @@
($builderStore.previewType === "layout" || insideScreenslot) &&
!isBlock
$: editing = editable && selected && $builderStore.editMode
- $: draggable = !inDragPath && interactive && !isLayout && !isScreen
+ $: draggable =
+ !inDragPath &&
+ interactive &&
+ !isLayout &&
+ !isScreen &&
+ definition?.draggable !== false
$: droppable = interactive && !isLayout && !isScreen
// Empty components are those which accept children but do not have any.
diff --git a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte
new file mode 100644
index 0000000000..200aaaaed9
--- /dev/null
+++ b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte
@@ -0,0 +1,297 @@
+
+
+
+ {#if error}
+
{error}
+ {/if}
+
+
+
+
+
diff --git a/packages/client/src/components/app/embedded-map/EmbeddedMapControls.js b/packages/client/src/components/app/embedded-map/EmbeddedMapControls.js
new file mode 100644
index 0000000000..ca1b1ed22a
--- /dev/null
+++ b/packages/client/src/components/app/embedded-map/EmbeddedMapControls.js
@@ -0,0 +1,192 @@
+import L from "leaflet"
+import screenfull from "screenfull"
+
+const createButton = function (html, title, className, container, fn) {
+ let link = L.DomUtil.create("a", className, container)
+ link.innerHTML = html
+ link.href = "#"
+ link.title = title
+
+ link.setAttribute("role", "button")
+ link.setAttribute("aria-label", title)
+
+ L.DomEvent.disableClickPropagation(link)
+ L.DomEvent.on(link, "click", L.DomEvent.stop)
+ L.DomEvent.on(link, "click", fn, this)
+ L.DomEvent.on(link, "click", this._refocusOnMap, this)
+
+ return link
+}
+
+// Full Screen Control
+
+const FullScreenControl = L.Control.extend({
+ options: {
+ position: "topright",
+ fullScreenContent:
+ '' +
+ '',
+ fullScreenTitle: "Enter Fullscreen",
+ },
+ onAdd: function () {
+ var fullScreenClassName = "leaflet-control-fullscreen",
+ container = L.DomUtil.create("div", fullScreenClassName + " leaflet-bar"),
+ options = this.options
+
+ this._fullScreenButton = this._createButton(
+ options.fullScreenContent,
+ options.fullScreenTitle,
+ "map-fullscreen",
+ container,
+ this._fullScreen
+ )
+
+ return container
+ },
+ _fullScreen: function () {
+ var map = this._map
+ if (screenfull.isEnabled) {
+ screenfull.toggle(map.getContainer())
+ }
+ },
+ _createButton: createButton,
+})
+
+const initFullScreenControl = () => {
+ L.Map.mergeOptions({
+ fullScreen: false,
+ })
+
+ L.Map.addInitHook(function () {
+ if (this.options.fullScreen) {
+ this.fullScreenControl = new FullScreenControl()
+ this.addControl(this.fullScreenControl)
+ } else {
+ this.fullScreenControl = null
+ }
+ })
+}
+
+// Location Control
+
+const LocationControl = L.Control.extend({
+ options: {
+ position: "topright",
+ locationContent:
+ '' +
+ '',
+ locationTitle: "Show Your Location",
+ },
+ onAdd: function () {
+ var locationClassName = "leaflet-control-location",
+ container = L.DomUtil.create("div", locationClassName + " leaflet-bar"),
+ options = this.options
+
+ this._locationButton = this._createButton(
+ options.locationContent,
+ options.locationTitle,
+ "map-location",
+ container,
+ this._location
+ )
+
+ this._updateDisabled()
+
+ return container
+ },
+ disable: function () {
+ this._disabled = true
+ this._updateDisabled()
+ return this
+ },
+ enable: function () {
+ this._disabled = false
+ this._updateDisabled()
+ return this
+ },
+ _location: function () {
+ if (this._disabled == true) {
+ return
+ }
+ this.disable()
+
+ const success = pos => {
+ this._map.closePopup()
+ if (typeof this.options.onLocationSuccess === "function") {
+ this.options.onLocationSuccess({
+ lat: pos.coords.latitude,
+ lng: pos.coords.longitude,
+ })
+ }
+ }
+
+ const error = err => {
+ if (typeof this.options.onLocationFail === "function") {
+ this.options.onLocationFail(err)
+ }
+ }
+
+ this._getPosition()
+ .then(success)
+ .catch(error)
+ .finally(() => {
+ this.enable()
+ })
+ },
+ _getPosition: function () {
+ var options = {
+ enableHighAccuracy: false,
+ timeout: 5000,
+ maximumAge: 30000,
+ }
+
+ return new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(resolve, reject, options)
+ })
+ },
+
+ _createButton: createButton,
+
+ _updateDisabled: function () {
+ let disabledClassName = "leaflet-disabled"
+ L.DomUtil.removeClass(this._locationButton, disabledClassName)
+ this._locationButton.setAttribute("aria-disabled", "false")
+
+ if (this._disabled) {
+ L.DomUtil.addClass(this._locationButton, disabledClassName)
+ this._locationButton.setAttribute("aria-disabled", "true")
+ }
+ },
+})
+
+const initLocationControl = () => {
+ L.Map.mergeOptions({
+ location: false,
+ onLocationFail: null,
+ onLocationSuccess: null,
+ })
+
+ L.Map.addInitHook(function () {
+ if (this.options.location) {
+ this.localControl = new LocationControl()
+ this.addControl(this.LocationControl)
+ } else {
+ this.localControl = null
+ }
+ })
+}
+
+const initMapControls = () => {
+ initFullScreenControl()
+ initLocationControl()
+}
+
+export {
+ initFullScreenControl,
+ initLocationControl,
+ initMapControls,
+ FullScreenControl,
+ LocationControl,
+}
diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js
index 5af62201e5..db8cc43ef6 100644
--- a/packages/client/src/components/app/index.js
+++ b/packages/client/src/components/app/index.js
@@ -31,6 +31,7 @@ export { default as cardstat } from "./CardStat.svelte"
export { default as spectrumcard } from "./SpectrumCard.svelte"
export { default as tag } from "./Tag.svelte"
export { default as markdownviewer } from "./MarkdownViewer.svelte"
+export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte"
export * from "./charts"
export * from "./forms"
export * from "./table"
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 9382519200..9ea91c7cd8 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -448,6 +448,13 @@ domelementtype@^2.0.1, domelementtype@^2.2.0:
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
+domhandler@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626"
+ integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==
+ dependencies:
+ domelementtype "^2.2.0"
+
domhandler@^4.2.0:
version "4.2.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
@@ -455,7 +462,12 @@ domhandler@^4.2.0:
dependencies:
domelementtype "^2.2.0"
-domutils@^2.6.0:
+domino@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/domino/-/domino-2.1.6.tgz#fe4ace4310526e5e7b9d12c7de01b7f485a57ffe"
+ integrity sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==
+
+domutils@^2.5.2, domutils@^2.6.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
@@ -489,6 +501,11 @@ entities@^2.0.0:
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+entities@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
+ integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
+
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -499,6 +516,11 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
estree-walker@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e"
@@ -519,6 +541,11 @@ estree-walker@^2.0.1:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+eventemitter3@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
+ integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo=
+
eventemitter3@^4.0.4:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
@@ -656,6 +683,14 @@ is-reference@^1.2.1:
dependencies:
"@types/estree" "*"
+is-regex@^1.0.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+ integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+ dependencies:
+ call-bind "^1.0.2"
+ has-tostringtag "^1.0.0"
+
is-resolvable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@@ -694,6 +729,13 @@ lilconfig@^2.0.3:
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
+linkify-it@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e"
+ integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==
+ dependencies:
+ uc.micro "^1.0.1"
+
loader-utils@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
@@ -730,6 +772,11 @@ mdn-data@2.0.14:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
+mdurl@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
+ integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
@@ -1117,6 +1164,27 @@ promise.series@^0.2.0:
resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd"
integrity sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=
+quill-delta@^3.6.2:
+ version "3.6.3"
+ resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032"
+ integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==
+ dependencies:
+ deep-equal "^1.0.1"
+ extend "^3.0.2"
+ fast-diff "1.1.2"
+
+quill@^1.3.7:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8"
+ integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==
+ dependencies:
+ clone "^2.1.1"
+ deep-equal "^1.0.1"
+ eventemitter3 "^2.0.3"
+ extend "^3.0.2"
+ parchment "^1.1.4"
+ quill-delta "^3.6.2"
+
randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -1271,6 +1339,27 @@ shortid@^2.2.15:
dependencies:
nanoid "^2.1.0"
+sirv-cli@^0.4.6:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/sirv-cli/-/sirv-cli-0.4.6.tgz#c28ab20deb3b34637f5a60863dc350f055abca04"
+ integrity sha512-/Vj85/kBvPL+n9ibgX6FicLE8VjidC1BhlX67PYPBfbBAphzR6i0k0HtU5c2arejfU3uzq8l3SYPCwl1x7z6Ww==
+ dependencies:
+ console-clear "^1.1.0"
+ get-port "^3.2.0"
+ kleur "^3.0.0"
+ local-access "^1.0.1"
+ sade "^1.4.0"
+ sirv "^0.4.6"
+ tinydate "^1.0.0"
+
+sirv@^0.4.6:
+ version "0.4.6"
+ resolved "https://registry.yarnpkg.com/sirv/-/sirv-0.4.6.tgz#185e44eb93d24009dd183b7494285c5180b81f22"
+ integrity sha512-rYpOXlNbpHiY4nVXxuDf4mXPvKz1reZGap/LkWp9TvcZ84qD/nPBjjH/6GZsgIjVMbOslnY8YYULAyP8jMn1GQ==
+ dependencies:
+ "@polka/url" "^0.5.0"
+ mime "^2.3.1"
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"