Strings and finalization + marker cluster

This commit is contained in:
Sergej Schwabauer 2022-03-03 15:21:39 +01:00
parent dce4eb1621
commit ace849b28e
13 changed files with 1096 additions and 164 deletions

View File

@ -14,3 +14,8 @@ exit
npm run serve npm run serve
npm install @pnp/spfx-controls-react --save --save-exact npm install @pnp/spfx-controls-react --save --save-exact
npm run serve npm run serve
exit
npm i react-leaflet-markercluster
npm run serve
npm install @pnp/spfx-property-controls --save --save-exact
npm run serve

View File

@ -14,6 +14,7 @@
"externals": {}, "externals": {},
"localizedResources": { "localizedResources": {
"MapWebPartStrings": "lib/webparts/map/loc/{locale}.js", "MapWebPartStrings": "lib/webparts/map/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js" "ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
} }
} }

486
package-lock.json generated
View File

@ -5169,6 +5169,22 @@
} }
} }
}, },
"@pnp/sp-clientsvc": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/sp-clientsvc/-/sp-clientsvc-1.3.11.tgz",
"integrity": "sha512-eIUnmDWjizcWJzhWxAbfsxEyHF1dabkGlihnDnlcYGhtvh8BwuM67A57qc5fbxzCS59c0YU57szB1EucoNmV4A==",
"requires": {
"tslib": "1.10.0"
}
},
"@pnp/sp-taxonomy": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/sp-taxonomy/-/sp-taxonomy-1.3.11.tgz",
"integrity": "sha512-shzCSjmOlr6mojCXJkfD8Xf9lJnhphq4Fj6mdUQGwpak+VIU+Fogf6AI0j6AReCKtKsKyqfud9X7C8tH07C3DA==",
"requires": {
"tslib": "1.10.0"
}
},
"@pnp/spfx-controls-react": { "@pnp/spfx-controls-react": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-3.6.0.tgz", "resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-3.6.0.tgz",
@ -5682,6 +5698,427 @@
} }
} }
}, },
"@pnp/spfx-property-controls": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/@pnp/spfx-property-controls/-/spfx-property-controls-3.5.0.tgz",
"integrity": "sha512-MifOtZ8gZKA1raHgl8K/MOx7/bL4KdhhKzKbPQ/IfwQYG4JvytaiYxmcmDccXr9xZImSPUcdxGoDvsvby7a04g==",
"requires": {
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-office-ui-fabric-core": "1.13.1",
"@microsoft/sp-property-pane": "1.13.1",
"@microsoft/sp-webpart-base": "1.13.1",
"@pnp/common": "1.3.11",
"@pnp/logging": "1.3.11",
"@pnp/odata": "1.3.11",
"@pnp/sp": "1.3.11",
"@pnp/sp-clientsvc": "1.3.11",
"@pnp/sp-taxonomy": "1.3.11",
"@pnp/telemetry-js": "2.0.0",
"@uifabric/icons": "7.5.17",
"lodash.omit": "4.5.0",
"markdown-to-jsx": "^6.11.4",
"office-ui-fabric-react": "7.174.1",
"react": "16.13.1",
"react-ace": "5.8.0",
"react-dom": "16.13.1"
},
"dependencies": {
"@microsoft/office-ui-fabric-react-bundle": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/office-ui-fabric-react-bundle/-/office-ui-fabric-react-bundle-1.13.1.tgz",
"integrity": "sha512-Tt99MShwPSqULImwsXT0umaG7mPTEwqbKq4ZvVJPjkydkpDZBTeDV6C+oGpqSa+wrbOi3Sk94Vyb71tY92W0VA==",
"requires": {
"@microsoft/sp-core-library": "1.13.1",
"@uifabric/icons": "7.6.0",
"office-ui-fabric-react": "7.176.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"tslib": "~1.10.0"
},
"dependencies": {
"@uifabric/icons": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.6.0.tgz",
"integrity": "sha512-Xx+CVMYOafJDijllYYkgE22lvKpKaodrB9XUgVSI77QveGcOV+x9z5FVa5CzwERb6Zjoafyj7q7SmH/EOi+AZw==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.1",
"tslib": "^1.10.0"
}
},
"office-ui-fabric-react": {
"version": "7.176.2",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.176.2.tgz",
"integrity": "sha512-ACOgx0ccx93NtRLWJBunJLwVdgIbsnzR/lbn6J+XYTINUrSR4DBZCuNoAzZVi8t1RYd6MnouLyyyEUWneNC9QQ==",
"requires": {
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.18.0",
"@fluentui/react-window-provider": "^1.0.2",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/foundation": "^7.10.0",
"@uifabric/icons": "^7.6.0",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/react-hooks": "^7.14.0",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.1",
"@uifabric/utilities": "^7.33.5",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
}
}
}
},
"@microsoft/sp-component-base": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-component-base/-/sp-component-base-1.13.1.tgz",
"integrity": "sha512-8jcxFs7GRPO4xyqs0U9bhxG0iS+5a2yCF0XanauzK5yweIVCjGUGatM581ynCP0hD+HshtdEYV0Y2zMZ9OGZxQ==",
"requires": {
"@microsoft/office-ui-fabric-react-bundle": "1.13.1",
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@microsoft/sp-dynamic-data": "1.13.1",
"@microsoft/sp-http": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-module-interfaces": "1.13.1",
"@microsoft/sp-page-context": "1.13.1",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-core-library": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-core-library/-/sp-core-library-1.13.1.tgz",
"integrity": "sha512-qTBMa3whxhMXn79YS2S+jq+a+iAId9v+WvZOvcgre4dI/7LL7zPhbsHQTfWhzzuB4f2tPxkldHO8eOv63Ud68Q==",
"requires": {
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-module-interfaces": "1.13.1",
"@microsoft/sp-odata-types": "1.13.1",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-diagnostics": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-diagnostics/-/sp-diagnostics-1.13.1.tgz",
"integrity": "sha512-KhzJo8kGL1M92WSDPhLlWyEnyAnkiRNMyQBb4eB9EYB8yZMhM2iBPpflIzatuJSOyNmq3jfmBS4G/ASg1tzyiQ==",
"requires": {
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1"
}
},
"@microsoft/sp-dynamic-data": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-dynamic-data/-/sp-dynamic-data-1.13.1.tgz",
"integrity": "sha512-x3Fk6dC5JgUOKhiFQ5JphMf9k+IhpLEWWk1bsmsLak3pCWeY9Xp3kR3SlrdPGaFtoa3wbFlEsYZ8Log+ut0b1w==",
"requires": {
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-module-interfaces": "1.13.1",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-http": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-http/-/sp-http-1.13.1.tgz",
"integrity": "sha512-MhlFCOhFUUt7HpG3oa9LSu7O1dqmyJfU2Zmk+uPrUnudy5JIL2K83g5rv2TK8YeGXDXxKEmqZsOsWP4ZHbo9YQ==",
"requires": {
"@microsoft/microsoft-graph-client": "~1.1.0",
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@types/adal-angular": "1.0.1",
"adal-angular": "1.0.16",
"msal": "1.4.13",
"msalLegacy": "npm:msal@1.4.12",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-loader": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-loader/-/sp-loader-1.13.1.tgz",
"integrity": "sha512-Srry9SqR2JQA4l8+I1LTdO0lIkrekxWOxo9/2RgCbQtMyG6sSSCvw3VxUqRZ6VEFQbJzmmGFCWZRLGOho0GMlw==",
"requires": {
"@microsoft/office-ui-fabric-react-bundle": "1.13.1",
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@microsoft/sp-dynamic-data": "1.13.1",
"@microsoft/sp-http": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-module-interfaces": "1.13.1",
"@microsoft/sp-odata-types": "1.13.1",
"@microsoft/sp-page-context": "1.13.1",
"@microsoft/sp-polyfills": "1.13.1",
"@rushstack/loader-raw-script": "1.3.175",
"@types/requirejs": "2.1.29",
"office-ui-fabric-react": "7.176.2",
"raw-loader": "~0.5.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"requirejs": "2.3.6",
"tslib": "~1.10.0"
},
"dependencies": {
"@uifabric/icons": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.6.2.tgz",
"integrity": "sha512-q7jEIwB5Tt2Egw9fqdgNPlBqBQ6hNNMQ3qs5y4S4YETRluB+AQTdKbrbYMsXo3Pm0FsJnRfiDojMzxusGX+MWA==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.20.0",
"tslib": "^1.10.0"
}
},
"office-ui-fabric-react": {
"version": "7.176.2",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.176.2.tgz",
"integrity": "sha512-ACOgx0ccx93NtRLWJBunJLwVdgIbsnzR/lbn6J+XYTINUrSR4DBZCuNoAzZVi8t1RYd6MnouLyyyEUWneNC9QQ==",
"requires": {
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.18.0",
"@fluentui/react-window-provider": "^1.0.2",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/foundation": "^7.10.0",
"@uifabric/icons": "^7.6.0",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/react-hooks": "^7.14.0",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.1",
"@uifabric/utilities": "^7.33.5",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
}
}
}
},
"@microsoft/sp-lodash-subset": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-lodash-subset/-/sp-lodash-subset-1.13.1.tgz",
"integrity": "sha512-SXRG4KO0k/VVqWwkFFpC0177GxOOZ8jvsJHkg+i42+FyHGs5Uu4itffLarraGnPMokZLxHpkig9xRc8ZDytN7A==",
"requires": {
"@types/lodash": "4.14.117",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-module-interfaces": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-module-interfaces/-/sp-module-interfaces-1.13.1.tgz",
"integrity": "sha512-qrzmQW+T/2+s9/Lu1I0G1chF5DPN4xcR7znn3N3IjuM67o6E4cdvKel+m66mrqqzSGfo4qmCb6lGYTTYZsS3vw=="
},
"@microsoft/sp-odata-types": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-odata-types/-/sp-odata-types-1.13.1.tgz",
"integrity": "sha512-+dHWQY4E44OT/2E8c+lsSAxQJiRiRgk4jLLpNeOj/NzlydS8D85iUyOGlKfbmRu/6ujrc31/L77juKKzPQh7kw==",
"requires": {
"tslib": "~1.10.0"
}
},
"@microsoft/sp-office-ui-fabric-core": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-office-ui-fabric-core/-/sp-office-ui-fabric-core-1.13.1.tgz",
"integrity": "sha512-vsOv5QLaII35Nznw/pOyFIFBtxXkzA1bSif4MaRJpnENSAR49+050ot/MWYRMBKoGiXq/5cZD8zOAiNMwASoxQ==",
"requires": {
"office-ui-fabric-core": "9.6.1-fluent2",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-page-context": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-page-context/-/sp-page-context-1.13.1.tgz",
"integrity": "sha512-BO8I0TWCaubYpMKZ3/eTeGC/tx1nRirk2ulkQ/ggk2PhuKJo6NGcXKo/JHe48Mje4Yh4d1r5FkjUGHoqKK3ruA==",
"requires": {
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@microsoft/sp-dynamic-data": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-odata-types": "1.13.1",
"tslib": "~1.10.0"
}
},
"@microsoft/sp-polyfills": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-polyfills/-/sp-polyfills-1.13.1.tgz",
"integrity": "sha512-sSN0su+v3tAzNTFt2QYJ/vzAIAZXZX6ox/1IFGFzNH0U30jdtfGG3vs5d45NkFkVLjDw9DL8Sc8uSi1lpEPNpA==",
"requires": {
"es6-promise": "4.2.4",
"es6-symbol": "3.1.3",
"tslib": "~1.10.0",
"whatwg-fetch": "2.0.3",
"whatwg-url": "4.7.1"
}
},
"@microsoft/sp-property-pane": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-property-pane/-/sp-property-pane-1.13.1.tgz",
"integrity": "sha512-eRqnrZGGv0nhDw4JdIi6wxCInFP2zlEZXMDFNaCTFJwM/ritRqGrHyVTxviENg7PXC5TTkVvIM9Hk/cNISRftg==",
"requires": {
"@microsoft/office-ui-fabric-react-bundle": "1.13.1",
"@microsoft/sp-component-base": "1.13.1",
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@microsoft/sp-dynamic-data": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"office-ui-fabric-react": "7.176.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"tslib": "~1.10.0"
},
"dependencies": {
"@uifabric/icons": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.6.2.tgz",
"integrity": "sha512-q7jEIwB5Tt2Egw9fqdgNPlBqBQ6hNNMQ3qs5y4S4YETRluB+AQTdKbrbYMsXo3Pm0FsJnRfiDojMzxusGX+MWA==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.20.0",
"tslib": "^1.10.0"
}
},
"office-ui-fabric-react": {
"version": "7.176.2",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.176.2.tgz",
"integrity": "sha512-ACOgx0ccx93NtRLWJBunJLwVdgIbsnzR/lbn6J+XYTINUrSR4DBZCuNoAzZVi8t1RYd6MnouLyyyEUWneNC9QQ==",
"requires": {
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.18.0",
"@fluentui/react-window-provider": "^1.0.2",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/foundation": "^7.10.0",
"@uifabric/icons": "^7.6.0",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/react-hooks": "^7.14.0",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.1",
"@uifabric/utilities": "^7.33.5",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
}
}
}
},
"@microsoft/sp-webpart-base": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@microsoft/sp-webpart-base/-/sp-webpart-base-1.13.1.tgz",
"integrity": "sha512-qBBtUaLGzP2rBuCrbwZQuEZqQtMmhF4MZQWGNvlDpJNOQEXJ6hj2vAnKxJjlTXdttWbfUdMWsyOx1Nb0bxOACg==",
"requires": {
"@microsoft/sp-component-base": "1.13.1",
"@microsoft/sp-core-library": "1.13.1",
"@microsoft/sp-diagnostics": "1.13.1",
"@microsoft/sp-dynamic-data": "1.13.1",
"@microsoft/sp-http": "1.13.1",
"@microsoft/sp-loader": "1.13.1",
"@microsoft/sp-lodash-subset": "1.13.1",
"@microsoft/sp-module-interfaces": "1.13.1",
"@microsoft/sp-page-context": "1.13.1",
"@microsoft/sp-property-pane": "1.13.1",
"@microsoft/teams-js": "1.10.0",
"@types/office-js": "1.0.36",
"office-ui-fabric-react": "7.176.2",
"react": "16.13.1",
"react-dom": "16.13.1",
"tslib": "~1.10.0"
},
"dependencies": {
"@uifabric/icons": {
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.6.2.tgz",
"integrity": "sha512-q7jEIwB5Tt2Egw9fqdgNPlBqBQ6hNNMQ3qs5y4S4YETRluB+AQTdKbrbYMsXo3Pm0FsJnRfiDojMzxusGX+MWA==",
"requires": {
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.20.0",
"tslib": "^1.10.0"
}
},
"office-ui-fabric-react": {
"version": "7.176.2",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-7.176.2.tgz",
"integrity": "sha512-ACOgx0ccx93NtRLWJBunJLwVdgIbsnzR/lbn6J+XYTINUrSR4DBZCuNoAzZVi8t1RYd6MnouLyyyEUWneNC9QQ==",
"requires": {
"@fluentui/date-time-utilities": "^7.9.1",
"@fluentui/react-focus": "^7.18.0",
"@fluentui/react-window-provider": "^1.0.2",
"@microsoft/load-themed-styles": "^1.10.26",
"@uifabric/foundation": "^7.10.0",
"@uifabric/icons": "^7.6.0",
"@uifabric/merge-styles": "^7.19.2",
"@uifabric/react-hooks": "^7.14.0",
"@uifabric/set-version": "^7.0.24",
"@uifabric/styling": "^7.19.1",
"@uifabric/utilities": "^7.33.5",
"prop-types": "^15.7.2",
"tslib": "^1.10.0"
}
}
}
},
"@pnp/common": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/common/-/common-1.3.11.tgz",
"integrity": "sha512-RhYKcfMP+h0pAzORZRHSPPLOBB58djN/pfnorpWPjsx6ZxMqbiDqTzAtTF4m8z/mdNnxJr0Q3kwt4ImU3FjwnA==",
"requires": {
"adal-angular": "1.0.17",
"tslib": "1.10.0"
},
"dependencies": {
"adal-angular": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/adal-angular/-/adal-angular-1.0.17.tgz",
"integrity": "sha1-bpNuDkH5HTsqiOf/ypwvb29WLMQ="
}
}
},
"@pnp/logging": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-1.3.11.tgz",
"integrity": "sha512-hADlIXwvF/wjee7425nFJ6NhqaWpWTJ5yg02bpwBUsiSuFqEUf+LwuAcyHQre2lMs6KyNa65FWoRQok9BlZuxA==",
"requires": {
"tslib": "1.10.0"
}
},
"@pnp/odata": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-1.3.11.tgz",
"integrity": "sha512-yMaRiuVZRei2pkryCOqsw3ZXD2Lw30IJv136WQmQPQPOxG4cvsS9+woXkfMqbWV2KQ1evFUqVXbitIz6eDVfNA==",
"requires": {
"tslib": "1.10.0"
}
},
"@pnp/sp": {
"version": "1.3.11",
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.3.11.tgz",
"integrity": "sha512-NjdeGe81aukiSPelSPjgAFRC1+SrNPTXvTdEqTH+Q1ZvgNtk8bdZp6K6xf9emfeM2qZDOu9GpKZpg0W/emq++g==",
"requires": {
"tslib": "1.10.0"
}
},
"@rushstack/loader-raw-script": {
"version": "1.3.175",
"resolved": "https://registry.npmjs.org/@rushstack/loader-raw-script/-/loader-raw-script-1.3.175.tgz",
"integrity": "sha512-Rf+DewV7fMB5OX3yvhPNT5QRQgvI+2MWGbBmI0aYC5Aiax+RT5d+TN7CXket5CLFvT9/un3wN41vMtlnUbszKQ==",
"requires": {
"loader-utils": "~1.1.0"
}
},
"@uifabric/icons": {
"version": "7.5.17",
"resolved": "https://registry.npmjs.org/@uifabric/icons/-/icons-7.5.17.tgz",
"integrity": "sha512-2S1kse0gtseTuV2r59iWukLxxoOJ6GgP2Yhxt9oxzaP9QubpYdxCUepvJmfPQQvvy4GELdykDUWQ6/hbzliJyw==",
"requires": {
"@uifabric/set-version": "^7.0.23",
"@uifabric/styling": "^7.16.18",
"tslib": "^1.10.0"
}
},
"es6-promise": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ=="
},
"whatwg-fetch": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
"integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ="
}
}
},
"@pnp/telemetry-js": { "@pnp/telemetry-js": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/@pnp/telemetry-js/-/telemetry-js-2.0.0.tgz", "resolved": "https://registry.npmjs.org/@pnp/telemetry-js/-/telemetry-js-2.0.0.tgz",
@ -8870,6 +9307,11 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true "dev": true
}, },
"brace": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz",
"integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg="
},
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -17570,6 +18012,11 @@
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz",
"integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==" "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw=="
}, },
"leaflet.markercluster": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/leaflet.markercluster/-/leaflet.markercluster-1.5.3.tgz",
"integrity": "sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA=="
},
"left-pad": { "left-pad": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
@ -17856,6 +18303,11 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true "dev": true
}, },
"lodash.omit": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz",
"integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA="
},
"lodash.restparam": { "lodash.restparam": {
"version": "3.6.1", "version": "3.6.1",
"resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz",
@ -18033,6 +18485,15 @@
} }
} }
}, },
"markdown-to-jsx": {
"version": "6.11.4",
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.4.tgz",
"integrity": "sha512-3lRCD5Sh+tfA52iGgfs/XZiw33f7fFX9Bn55aNnVNUd2GzLDkOWyKYYD8Yju2B1Vn+feiEdgJs8T6Tg0xNokPw==",
"requires": {
"prop-types": "^15.6.2",
"unquote": "^1.1.0"
}
},
"matchdep": { "matchdep": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz",
@ -20910,6 +21371,17 @@
"resolved": "https://registry.npmjs.org/react-accessible-accordion/-/react-accessible-accordion-3.3.5.tgz", "resolved": "https://registry.npmjs.org/react-accessible-accordion/-/react-accessible-accordion-3.3.5.tgz",
"integrity": "sha512-yCh3tx+jNuOPs+m58LOBFTGDEaGvM8UfuCNznr855FDAWzwV8V/ZH/TVBvgqH0npP58KrrVrHpj4jcy0EE5hEw==" "integrity": "sha512-yCh3tx+jNuOPs+m58LOBFTGDEaGvM8UfuCNznr855FDAWzwV8V/ZH/TVBvgqH0npP58KrrVrHpj4jcy0EE5hEw=="
}, },
"react-ace": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/react-ace/-/react-ace-5.8.0.tgz",
"integrity": "sha1-hy2e6LZkMA7Vq57axiNLvpCDaDY=",
"requires": {
"brace": "^0.11.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.1.1",
"prop-types": "^15.5.8"
}
},
"react-addons-shallow-compare": { "react-addons-shallow-compare": {
"version": "15.6.3", "version": "15.6.3",
"resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.3.tgz", "resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.3.tgz",
@ -20957,6 +21429,17 @@
"@react-leaflet/core": "^1.1.1" "@react-leaflet/core": "^1.1.1"
} }
}, },
"react-leaflet-markercluster": {
"version": "3.0.0-rc1",
"resolved": "https://registry.npmjs.org/react-leaflet-markercluster/-/react-leaflet-markercluster-3.0.0-rc1.tgz",
"integrity": "sha512-wr8ERtx73sY0uVoQAM1v1vsA5Vsbdgyqc88h+Eo2kYRwNdkVTEOoUTnAh3CgGuOyP0Y9QLd2dKGupGkufpwryQ==",
"requires": {
"@react-leaflet/core": "^1.0.2",
"leaflet": "^1.6.0",
"leaflet.markercluster": "^1.4.1",
"react-leaflet": "^3.0.0"
}
},
"react-mentions": { "react-mentions": {
"version": "4.3.1", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/react-mentions/-/react-mentions-4.3.1.tgz", "resolved": "https://registry.npmjs.org/react-mentions/-/react-mentions-4.3.1.tgz",
@ -25265,8 +25748,7 @@
"unquote": { "unquote": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
"integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ="
"dev": true
}, },
"unset-value": { "unset-value": {
"version": "1.0.0", "version": "1.0.0",

View File

@ -16,12 +16,14 @@
"@microsoft/sp-property-pane": "1.14.0", "@microsoft/sp-property-pane": "1.14.0",
"@microsoft/sp-webpart-base": "1.14.0", "@microsoft/sp-webpart-base": "1.14.0",
"@pnp/spfx-controls-react": "3.6.0", "@pnp/spfx-controls-react": "3.6.0",
"@pnp/spfx-property-controls": "3.5.0",
"@spfxappdev/utility": "^1.1.0", "@spfxappdev/utility": "^1.1.0",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"office-ui-fabric-react": "7.174.1", "office-ui-fabric-react": "7.174.1",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-leaflet": "^3.2.5" "react-leaflet": "^3.2.5",
"react-leaflet-markercluster": "^3.0.0-rc1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.5", "@babel/core": "^7.17.5",

View File

@ -17,10 +17,15 @@
"supportsThemeVariants": true, "supportsThemeVariants": true,
"preconfiguredEntries": [{ "preconfiguredEntries": [{
"groupId": "142aa22c-9004-41c2-9821-2d78eed048d1", // SPFx App Dev "groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" }, "group": { "default": "Other" },
"title": { "default": "Map" }, "title": {
"description": { "default": "Map description" }, "default": "Interactive Map",
"de-de": "Interaktive Karte"
},
"description": {
"default": "Map description"
},
"officeFabricIconFontName": "MapPin", "officeFabricIconFontName": "MapPin",
"properties": { "properties": {
"title": "Map", "title": "Map",
@ -29,13 +34,17 @@
"center": [51.505, -0.09], "center": [51.505, -0.09],
"startZoom": 13, "startZoom": 13,
"maxZoom": 50, "maxZoom": 50,
"dragging": true,
"height": 400,
"scrollWheelZoom": true,
"plugins": { "plugins": {
"searchBox": false, "searchBox": false,
"markercluster": false, "markercluster": false,
"legend": false, "legend": false,
"zoomControl": true, "zoomControl": true
"scrollWheelZoom": false },
} "tileLayerUrl": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
"tileLayerAttribution": "&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors"
} }
}] }]
} }

View File

@ -10,6 +10,7 @@ import {
PropertyPaneLabel PropertyPaneLabel
} from '@microsoft/sp-property-pane'; } from '@microsoft/sp-property-pane';
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base'; import { IReadonlyTheme } from '@microsoft/sp-component-base';
@ -17,13 +18,13 @@ import * as strings from 'MapWebPartStrings';
import Map from './components/Map'; import Map from './components/Map';
import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps'; import { IMapProps, IMarker, IMarkerCategory } from './components/IMapProps';
import ManageMarkerCategoriesDialog, { IManageMarkerCategoriesDialogProps } from './components/ManageMarkerCategoriesDialog'; import ManageMarkerCategoriesDialog, { IManageMarkerCategoriesDialogProps } from './components/ManageMarkerCategoriesDialog';
import { isNullOrEmpty } from '@spfxappdev/utility';
export interface IMapPlugins { export interface IMapPlugins {
searchBox: boolean; searchBox: boolean;
markercluster: boolean; markercluster: boolean;
legend: boolean; legend: boolean;
zoomControl: boolean; zoomControl: boolean;
scrollWheelZoom: boolean;
} }
export interface IMapWebPartProps { export interface IMapWebPartProps {
@ -34,25 +35,25 @@ export interface IMapWebPartProps {
startZoom: number; startZoom: number;
maxZoom: number; maxZoom: number;
height: number; height: number;
scrollWheelZoom: boolean;
dragging: boolean;
showPopUp: boolean;
plugins: IMapPlugins; plugins: IMapPlugins;
tileLayerUrl: string;
tileLayerAttribution: string;
} }
export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> { export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps> {
private _isDarkTheme: boolean = false; private _isDarkTheme: boolean = false;
private _environmentMessage: string = '';
protected onInit(): Promise<void> { protected onInit(): Promise<void> {
this._environmentMessage = this._getEnvironmentMessage(); return super.onInit();
return super.onInit();
} }
public render(): void { public render(): void {
console.log("render", this.properties);
const element: React.ReactElement<IMapProps> = React.createElement( const element: React.ReactElement<IMapProps> = React.createElement(
Map, Map,
{ {
@ -64,6 +65,11 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
title: this.properties.title, title: this.properties.title,
height: this.properties.height, height: this.properties.height,
plugins: this.properties.plugins, plugins: this.properties.plugins,
tileLayerUrl: isNullOrEmpty(this.properties.tileLayerUrl) ? "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" : this.properties.tileLayerUrl,
tileLayerAttribution: isNullOrEmpty(this.properties.tileLayerAttribution) ? "&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors" : this.properties.tileLayerAttribution,
dragging: this.properties.dragging,
scrollWheelZoom: this.properties.scrollWheelZoom,
showPopUp: this.properties.showPopUp,
onMarkerCollectionChanged: (markerItems: IMarker[]) => { onMarkerCollectionChanged: (markerItems: IMarker[]) => {
this.properties.markerItems = markerItems; this.properties.markerItems = markerItems;
@ -94,15 +100,6 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
this.properties.markerCategories = markerCategories; this.properties.markerCategories = markerCategories;
this.render(); this.render();
} }
private _getEnvironmentMessage(): string {
if (!!this.context.sdks.microsoftTeams) { // running in Teams
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
}
return this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment;
}
protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void { protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
if (!currentTheme) { if (!currentTheme) {
return; return;
@ -130,35 +127,67 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
return { return {
pages: [ pages: [
{ {
header: {
description: strings.PropertyPaneDescription
},
groups: [ groups: [
{ {
groupName: strings.BasicGroupName, groupName: strings.WebPartPropertyGroupMapSettings,
groupFields: [ groupFields: [
PropertyPaneToggle('plugins.searchBox', { // PropertyPaneToggle('plugins.searchBox', {
label: "searchBox" // label: "searchBox"
// }),
PropertyPaneWebPartInformation({
description: `<div class='wp-settings-info'>${strings.WebPartPropertySettingsInfoLabel}</div>`,
key: 'Info_For_3f860b48-1dc3-496d-bd28-b145672289cc'
}), }),
PropertyPaneSlider('maxZoom', {
label: strings.WebPartPropertyMaxZoomLabel,
max: 30,
min: 5,
step: 1
}),
PropertyPaneSlider('height', {
label: strings.WebPartPropertyHeightLabel,
min: 100,
max: 1200,
step: 50
}),
PropertyPaneTextField('tileLayerUrl', {
label: strings.WebPartPropertyTileLayerUrlLabel
}),
PropertyPaneTextField('tileLayerAttribution', {
label: strings.WebPartPropertyTileLayerAttributionLabel
}),
PropertyPaneToggle('scrollWheelZoom', {
label: strings.WebPartPropertyScrollWheelZoomLabel,
}),
PropertyPaneToggle('dragging', {
label: strings.WebPartPropertyMapDraggingLabel,
}),
PropertyPaneToggle('showPopUp', {
label: strings.WebPartPropertyShowPopUpLabel,
}),
]
},
{
isCollapsed: true,
groupName: strings.WebPartPropertyGroupPlugins,
groupFields: [
PropertyPaneToggle('plugins.markercluster', { PropertyPaneToggle('plugins.markercluster', {
label: "markercluster" label: strings.WebPartPropertyPluginMarkerClusterLabel,
}),
PropertyPaneToggle('plugins.legend', {
label: "legend"
}), }),
PropertyPaneToggle('plugins.zoomControl', { PropertyPaneToggle('plugins.zoomControl', {
label: "zoomControl" label: strings.WebPartPropertyPluginZoomControlLabel
}),
PropertyPaneToggle('plugins.scrollWheelZoom', {
label: "scrollWheelZoom",
}), }),
]
},
{
isCollapsed: true,
groupName: strings.WebPartPropertyGroupCategories,
groupFields: [
PropertyPaneButton(null, { PropertyPaneButton(null, {
text: "Manage categories", text: strings.WebPartPropertyButtonManageCategories,
onClick: (val: any) => { onClick: (val: any) => {
const dummyElement: HTMLDivElement = document.createElement("div"); const dummyElement: HTMLDivElement = document.createElement("div");
dummyElement.id = "teeeeest";
let reactInstance = null;
document.body.appendChild(dummyElement); document.body.appendChild(dummyElement);
const element: React.ReactElement<IManageMarkerCategoriesDialogProps> = React.createElement(ManageMarkerCategoriesDialog, { const element: React.ReactElement<IManageMarkerCategoriesDialogProps> = React.createElement(ManageMarkerCategoriesDialog, {
@ -176,10 +205,24 @@ export default class MapWebPart extends BaseClientSideWebPart<IMapWebPartProps>
return null; return null;
} }
}),
PropertyPaneToggle('plugins.legend', {
label: strings.WebPartPropertyPluginLegendLabel
})
]
},
{
groupName: strings.WebPartPropertyGroupAbout,
groupFields: [
PropertyPaneWebPartInformation({
description: `This is a <strong>demo webpart</strong>, used to demonstrate all the <a href="https://aka.ms/sppnp">PnP</a> property controls`,
moreInfoLink: `https://pnp.github.io/sp-dev-fx-property-controls/`,
key: '3f860b48-1dc3-496d-bd28-b145672289cc'
}) })
] ]
} }
] ],
displayGroupsAsAccordion: true,
} }
] ]
}; };

View File

@ -1,23 +1,26 @@
import * as React from 'react'; import * as React from 'react';
import { IMarker, IMarkerCategory, MarkerType } from './IMapProps'; import { IMarker, IMarkerCategory, MarkerType } from './IMapProps';
import styles from './Map.module.scss'; import './Map.module.scss';
import { clone } from '@microsoft/sp-lodash-subset'; import { clone } from '@microsoft/sp-lodash-subset';
import { Icon, Panel, Dialog, TextField, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, Label } from 'office-ui-fabric-react'; import { Icon, Panel, TextField, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, Label, TooltipHost } from 'office-ui-fabric-react';
import { Guid } from '@microsoft/sp-core-library'; import { Guid } from '@microsoft/sp-core-library';
import { isNullOrEmpty, isFunction } from '@spfxappdev/utility'; import { isNullOrEmpty, isFunction } from '@spfxappdev/utility';
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker'; import { InlineColorPicker } from '@src/components/inlineColorPicker/InlineColorPicker';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText"; import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import '@spfxappdev/utility/lib/extensions/StringExtensions'; import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions'; import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import ManageMarkerCategoriesDialog from './ManageMarkerCategoriesDialog'; import ManageMarkerCategoriesDialog from './ManageMarkerCategoriesDialog';
import { MarkerIcon } from './MarkerIcon'; import { MarkerIcon } from './MarkerIcon';
import * as strings from 'MapWebPartStrings';
export interface IAddOrEditPanelProps { export interface IAddOrEditPanelProps {
markerItem: IMarker; markerItem: IMarker;
markerCategories: IMarkerCategory[]; markerCategories: IMarkerCategory[];
onDismiss(); onDismiss();
onMarkerChanged(markerItem: IMarker, isNewMarker: boolean); onMarkerChanged(markerItem: IMarker, isNewMarker: boolean);
onDeleteMarker(markerItem: IMarker);
onChangePositionClick(markerItem: IMarker);
onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]); onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]);
} }
@ -26,6 +29,7 @@ interface IAddOrEditPanelState {
markerCategories: IMarkerCategory[]; markerCategories: IMarkerCategory[];
isSaveButtonDisabled: boolean; isSaveButtonDisabled: boolean;
isManageCategoriesDialogVisible: boolean; isManageCategoriesDialogVisible: boolean;
} }
export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps, IAddOrEditPanelState> { export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps, IAddOrEditPanelState> {
@ -42,23 +46,23 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
private readonly headerText: string; private readonly headerText: string;
private markerTypeOptions: IChoiceGroupOption[] = [ private markerTypeOptions: IChoiceGroupOption[] = [
{ key: 'Panel', text: 'Panel', iconProps: { iconName: 'SidePanel' } }, { key: 'Panel', text: strings.ChoiceGroupPanelLabel, iconProps: { iconName: 'SidePanel' } },
{ key: 'Dialog', text: 'Dialog', iconProps: { iconName: 'Favicon' } }, { key: 'Dialog', text: strings.ChoiceGroupDialogLabel, iconProps: { iconName: 'Favicon' } },
{ key: 'Url', text: 'Url', iconProps: { iconName: 'Link' } }, { key: 'Url', text: strings.ChoiceGroupUrlLabel, iconProps: { iconName: 'Link' } },
{ key: 'None', text: 'None (not clickable)', iconProps: { iconName: 'FieldEmpty' } }, { key: 'None', text: strings.ChoiceGroupNoneLabel, iconProps: { iconName: 'FieldEmpty' } },
]; ];
private urlOptions: IChoiceGroupOption[] = [ private urlOptions: IChoiceGroupOption[] = [
{ key: '_self', text: 'Open in same window' }, { key: '_self', text: strings.ChoiceGroupTargetSelfLabel },
{ key: '_blank', text: 'Open in new window' }, { key: '_blank', text: strings.ChoiceGroupTargetBlankLabel },
{ key: 'embedded', text: 'Embedded (Dialog/iFrame)' }, { key: 'embedded', text: strings.ChoiceGroupTargetEmbeddedLabel },
]; ];
constructor(props: IAddOrEditPanelProps) { constructor(props: IAddOrEditPanelProps) {
super(props); super(props);
this.isNewMarker = this.props.markerItem.id.Equals(Guid.empty.toString()); this.isNewMarker = this.props.markerItem.id.Equals(Guid.empty.toString());
this.headerText = !this.isNewMarker ? "Bearbeiten" : "Neu"; this.headerText = this.isNewMarker ? strings.PanelHeaderNewLabel : strings.PanelHeaderEditLabel;
} }
@ -72,7 +76,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
isOpen={true} isOpen={true}
onDismiss={() => { this.onConfigPanelDismiss() }} onDismiss={() => { this.onConfigPanelDismiss() }}
headerText={this.headerText} headerText={this.headerText}
closeButtonAriaLabel="Close" closeButtonAriaLabel={strings.CloseLabel}
onRenderFooterContent={(props: IPanelProps) => { onRenderFooterContent={(props: IPanelProps) => {
return this.renderPanelFooter(); return this.renderPanelFooter();
}} }}
@ -81,7 +85,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
isFooterAtBottom={true} isFooterAtBottom={true}
> >
<Label> <Label>
Category {strings.LabelCategory}
<span <span
onClick={() => { onClick={() => {
this.setState({ this.setState({
@ -89,11 +93,11 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
}); });
}} }}
className='manage-categories-label'> className='manage-categories-label'>
(Manage) ({strings.LabelManage})
</span> </span>
</Label> </Label>
<Dropdown <Dropdown
placeholder="Select a category" placeholder={strings.PlaceholderSelectACategory}
defaultSelectedKey={selectedCatId} defaultSelectedKey={selectedCatId}
onChange={(ev: any, option: IDropdownOption) => { onChange={(ev: any, option: IDropdownOption) => {
this.state.markerItem.categoryId = option.key.toString(); this.state.markerItem.categoryId = option.key.toString();
@ -106,7 +110,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
options={this.categoryOptions} options={this.categoryOptions}
/> />
<ChoiceGroup <ChoiceGroup
label="Type of marker (on click)" label={strings.LabelMarkerType}
defaultSelectedKey={this.state.markerItem.type} defaultSelectedKey={this.state.markerItem.type}
onChange={(ev: any, option: IChoiceGroupOption) => { onChange={(ev: any, option: IChoiceGroupOption) => {
this.state.markerItem.type = option.key.toString() as MarkerType; this.state.markerItem.type = option.key.toString() as MarkerType;
@ -131,18 +135,28 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
} }
private renderPanelFooter(): JSX.Element { private renderPanelFooter(): JSX.Element {
return (<div> return (<div className='panel-footer'>
<PrimaryButton disabled={this.state.isSaveButtonDisabled} onClick={() => { <PrimaryButton
text={strings.SaveLabel}
disabled={this.state.isSaveButtonDisabled}
onClick={() => {
if(this.isNewMarker) { if(this.isNewMarker) {
this.state.markerItem.id = Guid.newGuid().toString(); this.state.markerItem.id = Guid.newGuid().toString();
} }
this.onSaveMarkerClick(this.state.markerItem); this.onSaveMarkerClick(this.state.markerItem);
}}> }}
Save />
</PrimaryButton>
<DefaultButton onClick={() => { this.onConfigPanelDismiss(); }}>Cancel</DefaultButton> {!this.isNewMarker &&
<>
<DefaultButton text={strings.DeleteLabel} onClick={() => { this.onDeleteMarkerClick(this.state.markerItem); }} />
<DefaultButton text={strings.ChangePositionLabel} onClick={() => { this.onChangePositionClick(this.state.markerItem); }} />
</>
}
<DefaultButton text={strings.CancelLabel} onClick={() => { this.onConfigPanelDismiss(); }} />
</div>); </div>);
} }
@ -155,7 +169,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
return ( return (
<> <>
<InlineColorPicker <InlineColorPicker
label='Marker Color' label={strings.LabelMarkerColor}
alphaType='none' alphaType='none'
color={getColorFromString(this.state.markerItem.iconProperties.markerColor)} color={getColorFromString(this.state.markerItem.iconProperties.markerColor)}
onChange={(ev: any, color: IColor) => { onChange={(ev: any, color: IColor) => {
@ -167,7 +181,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
}} }}
/> />
<TextField label='Icon' description='leaf blank for none' defaultValue={this.state.markerItem.iconProperties.iconName} onChange={(ev: any, iconName: string) => { <TextField label={strings.LabelIcon} description={strings.LabelLeaveEmpty} defaultValue={this.state.markerItem.iconProperties.iconName} onChange={(ev: any, iconName: string) => {
this.state.markerItem.iconProperties.iconName = iconName; this.state.markerItem.iconProperties.iconName = iconName;
this.setState({ this.setState({
markerItem: this.state.markerItem, markerItem: this.state.markerItem,
@ -176,7 +190,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
}} /> }} />
<InlineColorPicker <InlineColorPicker
label='Icon Color' label={strings.LabelIconColor}
alphaType='none' alphaType='none'
color={getColorFromString(this.state.markerItem.iconProperties.iconColor)} color={getColorFromString(this.state.markerItem.iconProperties.iconColor)}
onChange={(ev: any, color: IColor) => { onChange={(ev: any, color: IColor) => {
@ -189,7 +203,13 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
isDisbaled={isNullOrEmpty(this.state.markerItem.iconProperties.iconName)} isDisbaled={isNullOrEmpty(this.state.markerItem.iconProperties.iconName)}
/> />
<TextField label='Popup Text' description='leaf blank for none' defaultValue={this.state.markerItem.popuptext} onChange={(ev: any, popuptext: string) => { <Label>
{strings.LabelTooltip}
<TooltipHost content={strings.TooltipInfo}>
<Icon className='info-tooltip' iconName='Info' />
</TooltipHost>
</Label>
<TextField description={strings.LabelLeaveEmptyTooltip} defaultValue={this.state.markerItem.popuptext} onChange={(ev: any, popuptext: string) => {
this.state.markerItem.popuptext = popuptext; this.state.markerItem.popuptext = popuptext;
this.setState({ this.setState({
markerItem: this.state.markerItem, markerItem: this.state.markerItem,
@ -197,7 +217,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
}); });
}} /> }} />
<Label>Vorschau</Label> <Label>{strings.LabelPreview}</Label>
<div style={{position: "relative", height: "36px", }}> <div style={{position: "relative", height: "36px", }}>
<div style={{position: "absolute"}}> <div style={{position: "absolute"}}>
<MarkerIcon {...this.state.markerItem.iconProperties} /> <MarkerIcon {...this.state.markerItem.iconProperties} />
@ -213,8 +233,10 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
return (<></>); return (<></>);
} }
const headerLabel: string = this.state.markerItem.type == "Dialog" ? strings.LabelDialogHeader : strings.LabelPanelHeader
return (<> return (<>
<TextField label='Panel Header' defaultValue={this.state.markerItem.markerClickProps.content.headerText} onChange={(ev: any, headerText: string) => { <TextField label={headerLabel} defaultValue={this.state.markerItem.markerClickProps.content.headerText} onChange={(ev: any, headerText: string) => {
this.state.markerItem.markerClickProps.content.headerText = headerText; this.state.markerItem.markerClickProps.content.headerText = headerText;
this.setState({ this.setState({
markerItem: this.state.markerItem, markerItem: this.state.markerItem,
@ -222,7 +244,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
}); });
}} /> }} />
<Label>Content</Label> <Label>{strings.LabelContent}</Label>
<RichText isEditMode={true} value={this.state.markerItem.markerClickProps.content.html} onChange={(content: string): string => { <RichText isEditMode={true} value={this.state.markerItem.markerClickProps.content.html} onChange={(content: string): string => {
this.state.markerItem.markerClickProps.content.html = content; this.state.markerItem.markerClickProps.content.html = content;
this.setState({ this.setState({
@ -244,7 +266,7 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
return ( return (
<> <>
<TextField label='Url' type='url' defaultValue={this.state.markerItem.markerClickProps.url.href} onChange={(ev: any, url: string) => { <TextField label={strings.LabelUrl} type='url' defaultValue={this.state.markerItem.markerClickProps.url.href} onChange={(ev: any, url: string) => {
this.state.markerItem.markerClickProps.url.href = url; this.state.markerItem.markerClickProps.url.href = url;
this.setState({ this.setState({
markerItem: this.state.markerItem, markerItem: this.state.markerItem,
@ -308,7 +330,21 @@ export default class AddOrEditPanel extends React.Component<IAddOrEditPanelProps
private onSaveMarkerClick(marker: IMarker): void { private onSaveMarkerClick(marker: IMarker): void {
if(isFunction(this.props.onMarkerChanged)) { if(isFunction(this.props.onMarkerChanged)) {
this.props.onMarkerChanged(this.state.markerItem, this.isNewMarker); this.props.onMarkerChanged(marker, this.isNewMarker);
}
}
private onDeleteMarkerClick(marker: IMarker): void {
if(isFunction(this.props.onDeleteMarker)) {
this.props.onDeleteMarker(marker);
}
}
private onChangePositionClick(marker: IMarker): void {
if(isFunction(this.props.onChangePositionClick)) {
this.props.onChangePositionClick(marker);
} }
} }

View File

@ -52,7 +52,13 @@ export interface IMapProps {
maxZoom?: number; maxZoom?: number;
title?: string; title?: string;
height: number; height: number;
dragging: boolean;
scrollWheelZoom: boolean;
plugins: IMapPlugins; plugins: IMapPlugins;
tileLayerUrl: string;
tileLayerAttribution: string;
showPopUp: boolean;
onMarkerCollectionChanged(markerItems: IMarker[]); onMarkerCollectionChanged(markerItems: IMarker[]);
onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]); onMarkerCategoriesChanged(markerCategories: IMarkerCategory[]);

View File

@ -1,16 +1,16 @@
import * as React from 'react'; import * as React from 'react';
import { IMarker, IMarkerCategory, MarkerType } from './IMapProps'; import { IMarkerCategory } from './IMapProps';
import styles from './Map.module.scss'; import './Map.module.scss';
import { clone } from '@microsoft/sp-lodash-subset'; import { clone } from '@microsoft/sp-lodash-subset';
import { Icon, Panel, Dialog, TextField, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, Label, DialogFooter, DialogContent, DialogType } from 'office-ui-fabric-react'; import { Icon, Dialog, TextField, PrimaryButton, DefaultButton, getColorFromString, IColor, DialogFooter, DialogContent, DialogType, MessageBar, TooltipHost } from 'office-ui-fabric-react';
import { Guid } from '@microsoft/sp-core-library'; import { Guid } from '@microsoft/sp-core-library';
import { isNullOrEmpty, isFunction } from '@spfxappdev/utility'; import { isNullOrEmpty, isFunction } from '@spfxappdev/utility';
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker'; import { InlineColorPicker } from '@src/components/inlineColorPicker/InlineColorPicker';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import '@spfxappdev/utility/lib/extensions/StringExtensions'; import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions'; import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import { IconButton } from '@microsoft/office-ui-fabric-react-bundle'; import { IconButton } from '@microsoft/office-ui-fabric-react-bundle';
import { MarkerIcon } from './MarkerIcon'; import { MarkerIcon } from './MarkerIcon';
import * as strings from 'MapWebPartStrings';
export interface IManageMarkerCategoriesDialogProps { export interface IManageMarkerCategoriesDialogProps {
markerCategories: IMarkerCategory[]; markerCategories: IMarkerCategory[];
@ -49,7 +49,7 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
hidden={!this.state.isDialogVisible} hidden={!this.state.isDialogVisible}
onDismiss={() => { this.onDialogDismiss(); }} onDismiss={() => { this.onDialogDismiss(); }}
dialogContentProps={{ dialogContentProps={{
title: "Manage Categories", title: strings.DialogTitleManageCategories,
type: DialogType.close type: DialogType.close
}} }}
minWidth={800} minWidth={800}
@ -60,15 +60,30 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
}} }}
> >
<DialogContent> <DialogContent>
<div className='spfxappdev-grid'> <div className='spfxappdev-grid'>
<MessageBar className='category-messagebar'>
{isNullOrEmpty(this.state.markerCategories) && <>{strings.InfoTextNoCategories} </>}{strings.InfoTextCategories}
</MessageBar>
{!isNullOrEmpty(this.state.markerCategories) &&
<>
<div className='spfxappdev-grid-row grid-header'> <div className='spfxappdev-grid-row grid-header'>
<div className='spfxappdev-grid-col spfxappdev-sm1'></div> <div className='spfxappdev-grid-col spfxappdev-sm1'></div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>Name</div> <div className='spfxappdev-grid-col spfxappdev-sm3'>{strings.LabelCategoryHeaderName}</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>Marker color</div> <div className='spfxappdev-grid-col spfxappdev-sm1'>{strings.LabelMarkerColor}</div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>Icon</div> <div className='spfxappdev-grid-col spfxappdev-sm3'>
<div className='spfxappdev-grid-col spfxappdev-sm1'>Icon Color</div> {strings.LabelIcon}
<div className='spfxappdev-grid-col spfxappdev-sm2'>Tooltip text</div> <TooltipHost content={strings.LabelLeaveEmpty}>
<Icon className='info-tooltip' iconName='Info' />
</TooltipHost></div>
<div className='spfxappdev-grid-col spfxappdev-sm1'>{strings.LabelIconColor}</div>
<div className='spfxappdev-grid-col spfxappdev-sm2'>
{strings.LabelTooltip}
<TooltipHost content={strings.TooltipInfoCategory}>
<Icon className='info-tooltip' iconName='Info' />
</TooltipHost>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm1'></div> <div className='spfxappdev-grid-col spfxappdev-sm1'></div>
</div> </div>
{this.state.markerCategories.map((cat: IMarkerCategory, index: number): JSX.Element => { {this.state.markerCategories.map((cat: IMarkerCategory, index: number): JSX.Element => {
@ -76,16 +91,21 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
{this.renderForm(cat, index)} {this.renderForm(cat, index)}
</div>) </div>)
})} })}
</>
}
<div className='spfxappdev-grid-row grid-footer'> <div className='spfxappdev-grid-row grid-footer'>
<div className='spfxappdev-grid-col spfxappdev-sm12'> <div className='spfxappdev-grid-col spfxappdev-sm12'>
<PrimaryButton onClick={() => { <PrimaryButton
text={strings.AddLabel}
onClick={() => {
this.onAddNewCatagoryButtonClick(); this.onAddNewCatagoryButtonClick();
}}>Add</PrimaryButton> }} />
</div> </div>
</div> </div>
</div> </div>
</DialogContent> </DialogContent>
@ -101,12 +121,12 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
isDialogVisible: false isDialogVisible: false
}); });
}} }}
text="Save" text={strings.SaveLabel}
disabled={this.state.isSaveButtonDisabled} disabled={this.state.isSaveButtonDisabled}
/> />
<DefaultButton onClick={() => { <DefaultButton onClick={() => {
this.onDialogDismiss(); this.onDialogDismiss();
}} text="Cancel" /> }} text={strings.CancelLabel} />
</DialogFooter> </DialogFooter>
</Dialog>); </Dialog>);

View File

@ -169,5 +169,87 @@
.map-legend { .map-legend {
display: flex; display: flex;
margin-top: 5px;
&-title {
font-weight: 700;
padding-right: 4px;
font-size: 12px;
}
&-marker-wrapper {
position: relative;
height: 36px;
float: left;
& > div {
position: absolute;
}
svg {
height: 18px !important;
}
.map-icon,
.map-icon i {
font-size: 6px;
}
.map-icon {
left: 3px;
top: 4px;
}
}
label {
margin: 0 10px 0 16px;
padding: 1px 0;
font-size: 12px;
}
}
.panel-footer {
button {
margin: 0 5px;
}
}
.leaflet-popup-close-button {
display: none;
}
.leaflet-popup-tip-container {
margin-top: -1px;
}
.change-position-popup {
text-align: center;
label {
font-size: 24px;
font-weight: 600;
}
button {
margin: 0 5px;
}
}
.info-tooltip {
font-size: 10px;
padding-left: 4px;
vertical-align: middle;
cursor:help;
}
.wp-settings-info {
font-weight: 600;
padding: 20px 0;
color: $ms-color-themeDarker;
}
.category-messagebar {
margin-bottom: 10px;
} }
} }

View File

@ -1,23 +1,24 @@
import * as React from 'react'; import * as React from 'react';
import * as ReactDom from 'react-dom'; import * as ReactDom from 'react-dom';
import styles from './Map.module.scss'; import styles from './Map.module.scss';
import { IMapProps, IMarker, IMarkerCategory, IMarkerIcon, MarkerType, IMarkerClickProps, IMarkerUrlProperties, IMarkerContentProperties, emptyMarkerItem } from './IMapProps'; import { IMapProps, IMarker, IMarkerCategory, IMarkerIcon, emptyMarkerItem } from './IMapProps';
import { clone } from '@microsoft/sp-lodash-subset'; import { clone } from '@microsoft/sp-lodash-subset';
import { MapContainer, TileLayer, Marker, Popup, Tooltip } from 'react-leaflet'; import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import "leaflet/dist/leaflet.css"; import "leaflet/dist/leaflet.css";
import "react-leaflet-markercluster/dist/styles.min.css";
import * as L from 'leaflet'; import * as L from 'leaflet';
import { Icon, ContextualMenu, ContextualMenuItemType, IContextualMenuItem, Panel, Dialog, IPanelProps, PrimaryButton, DefaultButton, IChoiceGroupOption, ChoiceGroup, IDropdownOption, Dropdown, getColorFromString, IColor, PanelType, DialogType, DialogContent, Label } from 'office-ui-fabric-react'; import { ContextualMenu, IContextualMenuItem, Panel, Dialog, IPanelProps, DefaultButton, PanelType, DialogType, DialogContent, Label, Separator, PrimaryButton } from 'office-ui-fabric-react';
import { randomString, isset, isNullOrEmpty, getDeepOrDefault, cssClasses } from '@spfxappdev/utility'; import { isset, isNullOrEmpty, getDeepOrDefault } from '@spfxappdev/utility';
import '@spfxappdev/utility/lib/extensions/StringExtensions'; import '@spfxappdev/utility/lib/extensions/StringExtensions';
import '@spfxappdev/utility/lib/extensions/ArrayExtensions'; import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import { DisplayMode, Guid } from '@microsoft/sp-core-library'; import { DisplayMode } from '@microsoft/sp-core-library';
import { InlineColorPicker, IInlineColorPickerProps } from '@src/components/inlineColorPicker/InlineColorPicker'
import { TextField } from '@microsoft/office-ui-fabric-react-bundle';
import { RichText } from "@pnp/spfx-controls-react/lib/RichText"; import { RichText } from "@pnp/spfx-controls-react/lib/RichText";
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle"; import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import AddOrEditPanel from './AddOrEditPanel'; import AddOrEditPanel from './AddOrEditPanel';
import { isFunction } from 'lodash'; import { isFunction } from 'lodash';
import { MarkerIcon } from './MarkerIcon'; import { MarkerIcon } from './MarkerIcon';
import MarkerClusterGroup from 'react-leaflet-markercluster';
import * as strings from 'MapWebPartStrings';
interface IMapState { interface IMapState {
markerItems: IMarker[]; markerItems: IMarker[];
@ -26,6 +27,7 @@ interface IMapState {
showAddOrEditMarkerPanel: boolean; showAddOrEditMarkerPanel: boolean;
currentMarker?: IMarker; currentMarker?: IMarker;
showClickContent: boolean; showClickContent: boolean;
changePositionMarkerId: string;
} }
export default class Map extends React.Component<IMapProps, IMapState> { export default class Map extends React.Component<IMapProps, IMapState> {
@ -34,7 +36,8 @@ export default class Map extends React.Component<IMapProps, IMapState> {
markerItems: clone(this.props.markerItems), markerItems: clone(this.props.markerItems),
markerCategories: clone(this.props.markerCategories), markerCategories: clone(this.props.markerCategories),
showAddOrEditMarkerPanel: false, showAddOrEditMarkerPanel: false,
showClickContent: false showClickContent: false,
changePositionMarkerId: '-1'
}; };
private allCatagories: Record<string, IMarkerCategory> = {}; private allCatagories: Record<string, IMarkerCategory> = {};
@ -42,14 +45,14 @@ export default class Map extends React.Component<IMapProps, IMapState> {
private menuItems: IContextualMenuItem[] = [ private menuItems: IContextualMenuItem[] = [
{ {
key: 'newItem', key: 'newItem',
text: 'Add a new marker', text: strings.ContextMenuAddNewMarkerLabel,
onClick: () => { onClick: () => {
this.onCreateNewMarkerContextMenuItemClick(); this.onCreateNewMarkerContextMenuItemClick();
} }
}, },
{ {
key: 'setStartView', key: 'setStartView',
text: 'Make this view as start position', text: strings.ContextMenuSetStartPositionLabel,
onClick: () => { onClick: () => {
this.onSetStartView(); this.onSetStartView();
} }
@ -58,6 +61,8 @@ export default class Map extends React.Component<IMapProps, IMapState> {
private map: L.Map = null; private map: L.Map = null;
private allLeafletMarker: Record<string, L.Marker> = {};
private lastLatLngRightClickPosition: L.LatLng; private lastLatLngRightClickPosition: L.LatLng;
@ -80,57 +85,84 @@ export default class Map extends React.Component<IMapProps, IMapState> {
public render(): React.ReactElement<IMapProps> { public render(): React.ReactElement<IMapProps> {
this.allLeafletMarker = {};
const isZoomControlEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "plugins.zoomControl", true);
const isScrollWheelZoomEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "scrollWheelZoom", true);
const isDraggingEnabled: boolean = this.props.isEditMode ? true : getDeepOrDefault<boolean>(this.props, "dragging", true);
// //
return ( return (
<div className={styles.map}> <div className={styles.map}>
<WebPartTitle displayMode={this.props.isEditMode?DisplayMode.Edit:DisplayMode.Read} {(this.props.isEditMode || (!this.props.isEditMode && !isNullOrEmpty(this.props.title))) &&
title={this.props.title} <WebPartTitle displayMode={this.props.isEditMode?DisplayMode.Edit:DisplayMode.Read}
updateProperty={this.props.onTitleUpdate} /> title={this.props.title}
updateProperty={this.props.onTitleUpdate} />
}
<MapContainer <MapContainer
zoomControl={getDeepOrDefault<boolean>(this.props, "plugins.zoomControl", true)} zoomControl={isZoomControlEnabled}
center={this.props.center} center={this.props.center}
zoom={this.props.zoom} zoom={this.props.zoom}
maxZoom={this.props.maxZoom} maxZoom={this.props.maxZoom}
scrollWheelZoom={isScrollWheelZoomEnabled}
touchZoom={isScrollWheelZoomEnabled}
dragging={isDraggingEnabled}
whenCreated={(map: L.Map) => { whenCreated={(map: L.Map) => {
map.on("contextmenu", (ev: L.LeafletEvent) => { map.on("contextmenu", (ev: L.LeafletEvent) => {
if (!this.props.isEditMode) {
return;
}
this.lastLatLngRightClickPosition = (ev as any).latlng; this.lastLatLngRightClickPosition = (ev as any).latlng;
this.setState({ this.setState({
rightMouseTarget: {x: ((ev as any).originalEvent as MouseEvent).clientX, y: ((ev as any).originalEvent as MouseEvent).clientY } rightMouseTarget: {
x: ((ev as any).originalEvent as MouseEvent).clientX,
y: ((ev as any).originalEvent as MouseEvent).clientY
}
}); });
}); });
this.map = map; this.map = map;
} }
} }
style={{height: "400px"}} style={{height: isNullOrEmpty(this.props.height) ? "400px" : `${this.props.height}px`}}
> >
<TileLayer <TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution={`<a href="https://spfx-app.dev/">SPFx-App.dev</a> | ${this.props.tileLayerAttribution}`}
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url={this.props.tileLayerUrl}
/> />
{this.renderMarker()} {this.props.plugins.markercluster &&
<MarkerClusterGroup>
{this.renderMarker()}
</MarkerClusterGroup>
}
{!this.props.plugins.markercluster &&
this.renderMarker()
}
</MapContainer> </MapContainer>
{this.renderLegend()} {this.renderLegend()}
<ContextualMenu {this.props.isEditMode &&
items={this.menuItems} <ContextualMenu
hidden={typeof this.state.rightMouseTarget == "undefined"} items={this.menuItems}
target={this.state.rightMouseTarget} hidden={typeof this.state.rightMouseTarget == "undefined"}
onItemClick={() => { target={this.state.rightMouseTarget}
onItemClick={() => {
}} }}
onDismiss={() => { onDismiss={() => {
this.setState({ this.setState({
rightMouseTarget: undefined rightMouseTarget: undefined
}); });
}} }}
/> />
}
{this.showAddOrEditMarkerPanel()} {this.showAddOrEditMarkerPanel()}
{this.showClickContent()} {this.showClickContent()}
</div> </div>
@ -144,13 +176,38 @@ export default class Map extends React.Component<IMapProps, IMapState> {
const useCategory: boolean = isset(this.allCatagories[marker.categoryId]); const useCategory: boolean = isset(this.allCatagories[marker.categoryId]);
const markerCategory: IMarkerCategory = useCategory ? this.allCatagories[marker.categoryId] : null; const markerCategory: IMarkerCategory = useCategory ? this.allCatagories[marker.categoryId] : null;
const popupText: string = !useCategory ? marker.popuptext : isNullOrEmpty(markerCategory.popuptext) ? markerCategory.name : markerCategory.popuptext; const popupText: string = !useCategory ? marker.popuptext : isNullOrEmpty(markerCategory.popuptext) ? markerCategory.name : markerCategory.popuptext;
const isDraggable: boolean = marker.id.Equals(this.state.changePositionMarkerId);
return ( return (
<Marker position={[marker.latitude, marker.longitude]} key={`marker_${index}`} icon={this.createIcon(marker, markerCategory)} eventHandlers={ <Marker
draggable={isDraggable}
position={[marker.latitude, marker.longitude]}
key={`marker_${marker.id}`}
icon={this.createIcon(marker, markerCategory)}
ref={(ref: L.Marker) => {
if(!isset(ref)) {
return;
}
this.allLeafletMarker[marker.id] = ref;
if(this.state.changePositionMarkerId.Equals(marker.id)) {
setTimeout(() => {
ref.openPopup();
}, 300);
}
}}
eventHandlers={
{ {
click: (ev: L.LeafletMouseEvent) => { click: (ev: L.LeafletMouseEvent) => {
if(this.state.changePositionMarkerId.length >= 32) {
return;
}
let showEditPanel: boolean = this.props.isEditMode; let showEditPanel: boolean = this.props.isEditMode;
this.setState({ this.setState({
@ -160,19 +217,94 @@ export default class Map extends React.Component<IMapProps, IMapState> {
}); });
}, },
mouseover: (ev: L.LeafletMouseEvent) => { mouseover: (ev: L.LeafletMouseEvent) => {
if(!this.props.showPopUp) {
return;
}
if(this.state.changePositionMarkerId.length >= 32) {
return;
}
(ev.target as any).openPopup(); (ev.target as any).openPopup();
}, },
mouseout: (ev: L.LeafletMouseEvent) => { mouseout: (ev: L.LeafletMouseEvent) => {
if(!this.props.showPopUp) {
return;
}
if(this.state.changePositionMarkerId.length >= 32) {
return;
}
(ev.target as any).closePopup(); (ev.target as any).closePopup();
}, },
dragend: (ev: L.DragEndEvent) => {
const currentMarker = (ev.target as any);
setTimeout(() => {
if(isset(marker)) {
currentMarker.openPopup();
}
}, 300);
}
} }
} }
> >
{!isNullOrEmpty(popupText) && {this.props.showPopUp && this.state.changePositionMarkerId != marker.id && !isNullOrEmpty(popupText) &&
<Popup> <Popup>
{popupText} {popupText}
</Popup> </Popup>
} }
{this.state.changePositionMarkerId == marker.id &&
<Popup>
<div className="change-position-popup">
<Label>{strings.LabelChangePosition}</Label>
<Separator />
<PrimaryButton
text={strings.SaveLabel}
onClick={() => {
const currentMarker = this.allLeafletMarker[marker.id];
const latLng: L.LatLng = currentMarker.getLatLng();
this.state.markerItems[index].latitude = latLng.lat;
this.state.markerItems[index].longitude = latLng.lng;
currentMarker.dragging.disable();
this.setState({
changePositionMarkerId: "-1",
showAddOrEditMarkerPanel: true,
markerItems: this.state.markerItems
});
if(isFunction(this.props.onMarkerCollectionChanged)) {
this.props.onMarkerCollectionChanged(this.state.markerItems);
}
}}
/>
<DefaultButton
text={strings.CancelLabel}
onClick={() => {
const currentMarker = this.allLeafletMarker[marker.id];
currentMarker.setLatLng([marker.latitude, marker.longitude]);
currentMarker.dragging.disable();
this.setState({
changePositionMarkerId: "-1",
showAddOrEditMarkerPanel: true
});
}}
/>
</div>
</Popup>
}
</Marker> </Marker>
); );
})} })}
@ -187,16 +319,16 @@ export default class Map extends React.Component<IMapProps, IMapState> {
return ( return (
<div className='map-legend'> <div className='map-legend'>
<span className="map-legend-title">{strings.LegendLabel}:</span>
{this.state.markerCategories.map((cat: IMarkerCategory): JSX.Element => { {this.state.markerCategories.map((cat: IMarkerCategory): JSX.Element => {
return ( return (
<div key={`legend_${cat.id}`}> <div key={`legend_${cat.id}`}>
<div style={{position: "relative", height: "36px", float: "left" }}> <div className='map-legend-marker-wrapper'>
<div style={{position: "absolute"}}> <div style={{}}>
<MarkerIcon {...cat.iconProperties} /> <MarkerIcon {...cat.iconProperties} />
</div> </div>
</div> </div>
<Label style={{margin: "0 36px 0 10px"}}>{cat.name}</Label> <Label style={{}}>{cat.name}</Label>
</div>) </div>)
})} })}
</div>); </div>);
@ -274,7 +406,7 @@ export default class Map extends React.Component<IMapProps, IMapState> {
private showAddOrEditMarkerPanel(): JSX.Element { private showAddOrEditMarkerPanel(): JSX.Element {
if(!this.state.showAddOrEditMarkerPanel) { if(!this.state.showAddOrEditMarkerPanel || !this.props.isEditMode) {
return (<></>); return (<></>);
} }
@ -283,6 +415,32 @@ export default class Map extends React.Component<IMapProps, IMapState> {
markerCategories={this.state.markerCategories} markerCategories={this.state.markerCategories}
markerItem={this.state.currentMarker} markerItem={this.state.currentMarker}
onDismiss={() => { this.onConfigPanelDismiss(); }} onDismiss={() => { this.onConfigPanelDismiss(); }}
onDeleteMarker={(markerItem: IMarker) => {
const markerIndex: number = this.state.markerItems.IndexOf(m => m.id == markerItem.id);
this.state.markerItems.RemoveAt(markerIndex);
if(isFunction(this.props.onMarkerCollectionChanged)) {
this.props.onMarkerCollectionChanged(this.state.markerItems);
}
this.state.rightMouseTarget = undefined;
this.onConfigPanelDismiss();
}}
onChangePositionClick={(markerItem: IMarker) => {
this.setState({
changePositionMarkerId: markerItem.id,
showAddOrEditMarkerPanel: false
});
}}
onMarkerCategoriesChanged={(markerCategories: IMarkerCategory[]) => { onMarkerCategoriesChanged={(markerCategories: IMarkerCategory[]) => {
this.state.markerCategories = markerCategories; this.state.markerCategories = markerCategories;
@ -304,7 +462,7 @@ export default class Map extends React.Component<IMapProps, IMapState> {
else { else {
const markerIndex: number = this.state.markerItems.IndexOf(m => m.id == markerItem.id); const markerIndex: number = this.state.markerItems.IndexOf(m => m.id == markerItem.id);
if(markerIndex>=0) { if(markerIndex >= 0) {
this.state.markerItems[markerIndex] = markerItem; this.state.markerItems[markerIndex] = markerItem;
} }
} }
@ -349,22 +507,11 @@ export default class Map extends React.Component<IMapProps, IMapState> {
markerIcon.createIcon = (oldIcon: HTMLElement) => { markerIcon.createIcon = (oldIcon: HTMLElement) => {
const wrapper = document.createElement("div"); const wrapper = document.createElement("div");
wrapper.classList.add("leaflet-marker-icon"); wrapper.classList.add("leaflet-marker-icon");
wrapper.dataset.markerid = marker.id;
wrapper.style.marginLeft = (markerIcon.options.iconAnchor[0] * -1) + "px"; wrapper.style.marginLeft = (markerIcon.options.iconAnchor[0] * -1) + "px";
wrapper.style.marginTop = (markerIcon.options.iconAnchor[1] * -1) + "px"; wrapper.style.marginTop = (markerIcon.options.iconAnchor[1] * -1) + "px";
const iconProperties: IMarkerIcon = isNullOrEmpty(markerCategory) ? marker.iconProperties : markerCategory.iconProperties; const iconProperties: IMarkerIcon = isNullOrEmpty(markerCategory) ? marker.iconProperties : markerCategory.iconProperties;
// wrapper.innerHTML = `<span>
// <svg height="36px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512" fill="${iconProperties.markerColor}">
// <!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
// <path d="M172.268 501.67C26.97 291.031 0 269.413 0 192 0 85.961 85.961 0 192 0s192 85.961 192 192c0 77.413-26.97 99.031-172.268 309.67-9.535 13.774-29.93 13.773-39.464 0z"/>
// </svg>
// <span class="map-icon" style="color: ${iconProperties.iconColor}"></span>
// </span>`;
// ReactDom.render(<Icon iconName={iconProperties.iconName} /> , wrapper.querySelector(".map-icon"));
ReactDom.render(<MarkerIcon {...iconProperties} />, wrapper); ReactDom.render(<MarkerIcon {...iconProperties} />, wrapper);
return wrapper; return wrapper;
@ -383,7 +530,6 @@ export default class Map extends React.Component<IMapProps, IMapState> {
} }
private onSetStartView(): void { private onSetStartView(): void {
console.log("SSC", this.map.getZoom(), this.map.getCenter())
if(isFunction(this.props.onStartViewSet)) { if(isFunction(this.props.onStartViewSet)) {
const zoom: number = this.map.getZoom(); const zoom: number = this.map.getZoom();
@ -398,4 +544,4 @@ export default class Map extends React.Component<IMapProps, IMapState> {
this.allCatagories[category.id] = category; this.allCatagories[category.id] = category;
}); });
} }
} }

View File

@ -1,11 +1,61 @@
define([], function() { define([], function() {
return { return {
"PropertyPaneDescription": "Description", "WebPartPropertyGroupMapSettings": "General settings",
"BasicGroupName": "Group Name", "WebPartPropertyGroupPlugins": "Plugins/Controls",
"DescriptionFieldLabel": "Description Field", "WebPartPropertyGroupCategories": "Categories",
"AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part", "WebPartPropertyGroupAbout": "About",
"AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app", "WebPartPropertyPluginSearchboxLabel": "Show Searchbox",
"AppSharePointEnvironment": "The app is running on SharePoint page", "WebPartPropertyPluginMarkerClusterLabel": "Enable marker cluster",
"AppTeamsTabEnvironment": "The app is running in Microsoft Teams" "WebPartPropertyPluginLegendLabel": "Show legend",
"WebPartPropertyButtonManageCategories": "Manage categories",
"WebPartPropertyPluginZoomControlLabel": "Show zoom control",
"WebPartPropertyScrollWheelZoomLabel": "Enable zoom on mouse wheel/touch",
"WebPartPropertyMapDraggingLabel": "Enable dragging in map",
"WebPartPropertyShowPopUpLabel": "Show tooltip when hovering the marker",
"WebPartPropertySettingsInfoLabel": "Most of these settings take effect only after a page refresh and only in display mode",
"WebPartPropertyMaxZoomLabel": "Maximum zoom level (depends on Tile layer)",
"WebPartPropertyHeightLabel": "Map height",
"WebPartPropertyTileLayerUrlLabel": "Tile layer URL (Examples can be found here: https://leaflet-extras.github.io/leaflet-providers/preview/)",
"WebPartPropertyTileLayerAttributionLabel": "Tile layer attribution",
"ContextMenuAddNewMarkerLabel": "Add a new marker here",
"ContextMenuSetStartPositionLabel": "Make this view as start position",
"LabelChangePosition": "Change position",
"SaveLabel": "Save",
"CancelLabel": "Cancel",
"CloseLabel": "Close",
"LegendLabel": "Legend",
"ChoiceGroupPanelLabel": "Panel",
"ChoiceGroupDialogLabel": "Dialog",
"ChoiceGroupUrlLabel": "Url",
"ChoiceGroupNoneLabel": "None (not clickable)",
"ChoiceGroupTargetSelfLabel": "Open in same window",
"ChoiceGroupTargetBlankLabel": "Open in new window",
"ChoiceGroupTargetEmbeddedLabel": "Embedded (Dialog/iFrame)",
"PanelHeaderNewLabel": "Create new marker",
"PanelHeaderEditLabel": "Edit marker",
"LabelCategory": "Category",
"LabelManage": "Manage",
"PlaceholderSelectACategory": "Select a category",
"LabelMarkerType": "Type of marker (on click)",
"DeleteLabel": "Delete",
"ChangePositionLabel": "Change Position",
"LabelMarkerColor": "Marker color",
"LabelIcon": "Icon",
"LabelLeaveEmpty": "Leave blank to show no icon",
"LabelIconColor": "Icon color",
"LabelTooltip": "Tooltip text",
"TooltipInfoCategory": "For categories, the name of the category is displayed when hovering over the marker (if enabled in the web part settings). This text can be overwritten here.",
"TooltipInfo": "When hovering over the marker, this text is displayed (if enabled in the webpart settings)",
"LabelLeaveEmptyTooltip": "Leave blank to show no tooltip",
"LabelPreview": "Preview",
"LabelPanelHeader": "Panel Header",
"LabelDialogHeader": "Dialog Header",
"LabelContent": "Content",
"LabelUrl": "Url",
"DialogTitleManageCategories": "Manage categories",
"InfoTextNoCategories": "Currently there are no categories.",
"InfoTextCategories": "Markers can be assigned to categories. If you change the category, this change will be applied to all markers assigned to this category. If you delete a category, the default configuration is used.",
"LabelCategoryHeaderName": "Name",
"AddLabel": "Add",
} }
}); });

View File

@ -1,11 +1,61 @@
declare interface IMapWebPartStrings { declare interface IMapWebPartStrings {
PropertyPaneDescription: string; WebPartPropertyGroupMapSettings: string;
BasicGroupName: string; WebPartPropertyGroupPlugins: string;
DescriptionFieldLabel: string; WebPartPropertyGroupCategories: string;
AppLocalEnvironmentSharePoint: string; WebPartPropertyGroupAbout: string;
AppLocalEnvironmentTeams: string; WebPartPropertyPluginSearchboxLabel: string;
AppSharePointEnvironment: string; WebPartPropertyPluginMarkerClusterLabel: string;
AppTeamsTabEnvironment: string; WebPartPropertyPluginLegendLabel: string;
WebPartPropertyButtonManageCategories: string;
WebPartPropertyPluginZoomControlLabel: string;
WebPartPropertyScrollWheelZoomLabel: string;
WebPartPropertyMapDraggingLabel: string;
WebPartPropertyShowPopUpLabel: string;
WebPartPropertySettingsInfoLabel: string;
WebPartPropertyMaxZoomLabel: string;
WebPartPropertyHeightLabel: string;
WebPartPropertyTileLayerUrlLabel: string;
WebPartPropertyTileLayerAttributionLabel: string;
ContextMenuAddNewMarkerLabel: string;
ContextMenuSetStartPositionLabel: string;
LabelChangePosition: string;
SaveLabel: string;
CancelLabel: string;
CloseLabel: string;
LegendLabel: string;
ChoiceGroupPanelLabel: string;
ChoiceGroupDialogLabel: string;
ChoiceGroupUrlLabel: string;
ChoiceGroupNoneLabel: string;
ChoiceGroupTargetSelfLabel: string;
ChoiceGroupTargetBlankLabel: string;
ChoiceGroupTargetEmbeddedLabel: string;
PanelHeaderNewLabel: string;
PanelHeaderEditLabel: string;
LabelCategory: string;
LabelManage: string;
PlaceholderSelectACategory: string;
LabelMarkerType: string;
DeleteLabel: string;
ChangePositionLabel: string;
LabelMarkerColor: string;
LabelIcon: string;
LabelLeaveEmpty: string;
LabelIconColor: string;
LabelTooltip: string;
TooltipInfoCategory: string;
TooltipInfo: string;
LabelLeaveEmptyTooltip: string;
LabelPreview: string;
LabelPanelHeader: string;
LabelDialogHeader: string;
LabelContent: string;
LabelUrl: string;
DialogTitleManageCategories: string;
InfoTextNoCategories: string;
InfoTextCategories: string;
LabelCategoryHeaderName: string;
AddLabel: string;
} }
declare module 'MapWebPartStrings' { declare module 'MapWebPartStrings' {