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"