From 042960e06005211a6d56cc9b583997f8de6eb54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacob=20Wodzyn=CC=81ski?= Date: Mon, 14 Mar 2022 15:35:58 +0100 Subject: [PATCH] Attribute table is now a full screen dialog: - This is part of #595. - Upon clicking on the 'show attribute table' button, a full screen dialog is shown. I've experimented with different solutions, including non-fullscreen. I'm still open for another implementation in floating Window though, if someone would like to give it a go. - SimpleDialog is _outside_ LayerSwitcher component. I started out with having it inside, but eventually I figured that there could be reasons to call this from other parts of code than just LayerSwitcher. So it's placed in components, subscribes on global observer and renders directly in . - I'll add a flag, on a per-layer-basis, that will control the visibility of the attribute table button. I'll call it 'showAttributeTableButton'. - We might like to make it a map setting too, we'll see. - Finally, added some Tooltips to LayerSwitcherItem. --- new-client/package-lock.json | 73 +++++++++--- new-client/package.json | 3 +- new-client/src/components/App.js | 2 + new-client/src/components/SimpleDialog.js | 74 ++++++++++++ .../LayerSwitcher/components/LayerItem.js | 106 ++++++++++-------- 5 files changed, 195 insertions(+), 63 deletions(-) create mode 100644 new-client/src/components/SimpleDialog.js diff --git a/new-client/package-lock.json b/new-client/package-lock.json index 829a6222f..cc0d33c48 100644 --- a/new-client/package-lock.json +++ b/new-client/package-lock.json @@ -18,6 +18,7 @@ "@mui/icons-material": "^5.3.1", "@mui/lab": "^5.0.0-alpha.67", "@mui/material": "^5.4.0", + "@mui/x-data-grid": "^5.6.1", "@nieuwlandgeo/sldreader": "^0.2.13", "@turf/buffer": "^6.5.0", "@turf/transform-translate": "^6.5.0", @@ -1827,9 +1828,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "dependencies": { "regenerator-runtime": "^0.13.4" }, @@ -3187,11 +3188,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.3.0.tgz", - "integrity": "sha512-O/E9IQKPMg0OrN7+gkn7Ga5o5WA2iXQGdyqNBFPNrYzxOvwzsEtM5K7MtTCGGYKFe8mhTRM0ZOjh5OM0dglw+Q==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", "dependencies": { - "@babel/runtime": "^7.16.7", + "@babel/runtime": "^7.17.2", "@types/prop-types": "^15.7.4", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.7.2", @@ -3208,6 +3209,29 @@ "react": "^17.0.0" } }, + "node_modules/@mui/x-data-grid": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.6.1.tgz", + "integrity": "sha512-qFyQtGi/ZL8zkmOp4dbAykiu8d/Dd9kt1QZrk7yq7NyMZdCLRRAGa7TH0sweOXJPzRIBmHEOUPVycRt20pVAGA==", + "dependencies": { + "@mui/utils": "^5.4.4", + "clsx": "^1.1.1", + "prop-types": "^15.8.1", + "reselect": "^4.1.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.2.8", + "@mui/system": "^5.2.8", + "react": "^17.0.2" + } + }, "node_modules/@nieuwlandgeo/sldreader": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/@nieuwlandgeo/sldreader/-/sldreader-0.2.13.tgz", @@ -15930,6 +15954,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "node_modules/reselect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", + "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -20244,9 +20273,9 @@ } }, "@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -21160,17 +21189,28 @@ "requires": {} }, "@mui/utils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.3.0.tgz", - "integrity": "sha512-O/E9IQKPMg0OrN7+gkn7Ga5o5WA2iXQGdyqNBFPNrYzxOvwzsEtM5K7MtTCGGYKFe8mhTRM0ZOjh5OM0dglw+Q==", + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.4.4.tgz", + "integrity": "sha512-hfYIXEuhc2mXMGN5nUPis8beH6uE/zl3uMWJcyHX0/LN/+QxO9zhYuV6l8AsAaphHFyS/fBv0SW3Nid7jw5hKQ==", "requires": { - "@babel/runtime": "^7.16.7", + "@babel/runtime": "^7.17.2", "@types/prop-types": "^15.7.4", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.7.2", "react-is": "^17.0.2" } }, + "@mui/x-data-grid": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-5.6.1.tgz", + "integrity": "sha512-qFyQtGi/ZL8zkmOp4dbAykiu8d/Dd9kt1QZrk7yq7NyMZdCLRRAGa7TH0sweOXJPzRIBmHEOUPVycRt20pVAGA==", + "requires": { + "@mui/utils": "^5.4.4", + "clsx": "^1.1.1", + "prop-types": "^15.8.1", + "reselect": "^4.1.5" + } + }, "@nieuwlandgeo/sldreader": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/@nieuwlandgeo/sldreader/-/sldreader-0.2.13.tgz", @@ -30323,6 +30363,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.5.tgz", + "integrity": "sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ==" + }, "resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", diff --git a/new-client/package.json b/new-client/package.json index d2f6f50df..465a427f1 100644 --- a/new-client/package.json +++ b/new-client/package.json @@ -47,10 +47,11 @@ "@mui/icons-material": "^5.3.1", "@mui/lab": "^5.0.0-alpha.67", "@mui/material": "^5.4.0", + "@mui/x-data-grid": "^5.6.1", "@nieuwlandgeo/sldreader": "^0.2.13", "@turf/buffer": "^6.5.0", - "@turf/union": "^6.5.0", "@turf/transform-translate": "^6.5.0", + "@turf/union": "^6.5.0", "abortcontroller-polyfill": "^1.7.3", "allsettled-polyfill": "^1.0.4", "date-fns": "^2.28.0", diff --git a/new-client/src/components/App.js b/new-client/src/components/App.js index 33b03c558..bcc52d362 100644 --- a/new-client/src/components/App.js +++ b/new-client/src/components/App.js @@ -18,6 +18,7 @@ import Introduction from "./Introduction"; import Announcement from "./Announcement/Announcement"; import Alert from "./Alert"; import PluginWindows from "./PluginWindows"; +import SimpleDialog from "./SimpleDialog"; import Search from "./Search/Search.js"; @@ -966,6 +967,7 @@ class App extends React.PureComponent { + {clean !== true && ( // NB: Special case here, important with !== true, because there is an edge-case where clean===undefined, and we don't want to match on that! ; +}); + +export default function SimpleDialog({ globalObserver }) { + const [open, setOpen] = React.useState(false); + const [content, setContent] = React.useState({ columns: [], rows: [] }); + const [title, setTitle] = React.useState(""); + + React.useEffect(() => { + globalObserver.subscribe( + "core.showAttributeTable", + ({ title, content }) => { + setTitle(title); + setContent(content); + setOpen(true); + } + ); + }, [globalObserver]); + + const handleClose = () => { + setOpen(false); + setTitle(""); + setContent({ columns: [], rows: [] }); + }; + + return ( + + + + + + + + {title} + + + +
+ +
+
+ ); +} diff --git a/new-client/src/plugins/LayerSwitcher/components/LayerItem.js b/new-client/src/plugins/LayerSwitcher/components/LayerItem.js index b1d6d61a3..94ec6855c 100644 --- a/new-client/src/plugins/LayerSwitcher/components/LayerItem.js +++ b/new-client/src/plugins/LayerSwitcher/components/LayerItem.js @@ -89,6 +89,7 @@ class LayerItem extends React.PureComponent { this.infoUrlText = layerInfo.infoUrlText; this.infoOwner = layerInfo.infoOwner; this.localObserver = layer.localObserver; + this.showAttributeTableButton = layer.showAttributeTableButton || false; this.usesMinMaxZoom = this.layerUsesMinMaxZoom(); this.minMaxZoomAlertOnToggleOnly = layer.get("minMaxZoomAlertOnToggleOnly"); @@ -296,33 +297,37 @@ class LayerItem extends React.PureComponent { renderInfoButton = () => { return this.isInfoEmpty() ? null : ( - - {this.state.infoVisible ? ( - - ) : ( - - )} - + + + {this.state.infoVisible ? ( + + ) : ( + + )} + + ); }; renderMoreButton = () => { return ( - - {this.state.toggleSettings ? ( - - ) : ( - - )} - + + + {this.state.toggleSettings ? ( + + ) : ( + + )} + + ); }; @@ -510,12 +515,18 @@ class LayerItem extends React.PureComponent { return {icon}; }; - showAttributeTable = async () => { + #showAttributeTable = async () => { try { const url = this.props.layer.getSource().get("url").replace("wms", "wfs"); const { LAYERS } = this.props.layer.getSource().getParams(); - const getFeatureUrl = `${url}?service=WFS&version=1.0.0&request=GetFeature&typeName=${LAYERS}&maxFeatures=5000&outputFormat=application%2Fjson`; - const describeFeatureTypeUrl = `${url}?service=WFS&version=1.0.0&request=DescribeFeatureType&typeName=${LAYERS}&outputFormat=application%2Fjson`; + // If URL already contains a query string part, we want to glue them together. + const glue = url.includes("?") ? "&" : "?"; + const getFeatureUrl = `${url}${glue}service=WFS&version=1.0.0&request=GetFeature&typeName=${LAYERS}&maxFeatures=5000&outputFormat=application%2Fjson`; + const describeFeatureTypeUrl = `${url}${glue}service=WFS&version=1.0.0&request=DescribeFeatureType&typeName=${LAYERS}&outputFormat=application%2Fjson`; + // TODO: QGIS Server doesn't support JSON response for DescribeFeatureType. We must + // fetch the result as GML2 and then parse it accordingly. This will require + // some more work than the current approach. + // const describeFeatureTypeUrl = `${url}${glue}service=WFS&version=1.0.0&request=DescribeFeatureType&typeName=${LAYERS}`; const r1 = await fetch(getFeatureUrl); const features = await r1.json(); const r2 = await fetch(describeFeatureTypeUrl); @@ -530,31 +541,25 @@ class LayerItem extends React.PureComponent { field: c.name, headerName: c.name, type: c.localType === "int" ? "number" : c.localType, // DataGrid wants 'number', not 'int', see https://mui.com/components/data-grid/columns/#column-types + flex: 1, }; }); - const rows = features.features.map((r) => r.properties); - - // These are now ready for MUI's DataGrid: - console.log("columns: ", columns); - console.log("rows: ", rows); - - /** - * TODO: - * Proposed next steps: - * Add a AttributeDialog.js component - * It should listen for an event on localObserver - * From here, we send an event with some payload: - * this.localObserver.publish("showAttributeTable", {columns, rows}); - * - * The Dialog takes care of closing itself, so we don't need to do anything more here. - * - * That's of course just one way. Another could be showing AttributeTable in - * a separate Window, hence allowing for displaying multiple tables next - * to each other. - */ + const rows = features.features.map((r, i) => { + return { ...r.properties, id: i }; + }); + + this.props.app.globalObserver.publish("core.showAttributeTable", { + title: `${this.caption} (${LAYERS})`, + content: { columns, rows }, + }); } catch (error) { console.error(error); + console.log(this); + this.props.enqueueSnackbar( + `Serverfel: attributtabellen för lagret "${this.caption}" kunde inte visas`, + { variant: "error" } + ); } }; @@ -615,10 +620,15 @@ class LayerItem extends React.PureComponent { )} {this.renderStatusButton()} {this.renderInfoButton()} + + {this.showAttributeTableButton && ( + + + + + + )} {this.renderMoreButton()} - - -