@eeacms/volto-eea-map 0.1.8 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,30 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [0.1.11](https://github.com/eea/volto-eea-map/compare/0.1.10...0.1.11)
8
+
9
+ - Legend for all layers [`2d9285e`](https://github.com/eea/volto-eea-map/commit/2d9285e12ea512b7a1f24300ad2f7b3072e334d5)
10
+
11
+ #### [0.1.10](https://github.com/eea/volto-eea-map/compare/0.1.9...0.1.10)
12
+
13
+ > 17 August 2022
14
+
15
+ - EEA core metadata adaptation [`#10`](https://github.com/eea/volto-eea-map/pull/10)
16
+ - EEA core metadata adaptation for map block to [`5fb5724`](https://github.com/eea/volto-eea-map/commit/5fb5724164e1e9b1ae5143f9bd565c28118be253)
17
+
18
+ #### [0.1.9](https://github.com/eea/volto-eea-map/compare/0.1.8...0.1.9)
19
+
20
+ > 12 August 2022
21
+
22
+ - Query builder for map layers queries [`#9`](https://github.com/eea/volto-eea-map/pull/9)
23
+ - pinpoint query builder [`6184b0e`](https://github.com/eea/volto-eea-map/commit/6184b0e1dcee48e7d34bf56bc1c878438d07f987)
24
+ - build query widget [`7a9d74a`](https://github.com/eea/volto-eea-map/commit/7a9d74a6fdac32a8b424c5ca97edd42df436cbe2)
25
+
7
26
  #### [0.1.8](https://github.com/eea/volto-eea-map/compare/0.1.7...0.1.8)
8
27
 
28
+ > 11 August 2022
29
+
30
+ - Integration & new block for Map Visualization content-type [`#8`](https://github.com/eea/volto-eea-map/pull/8)
9
31
  - remove title on map edit widget [`1d00707`](https://github.com/eea/volto-eea-map/commit/1d0070716a743ea7f26c5f877b723492319fcecd)
10
32
  - update query by stored value [`c0f6ddd`](https://github.com/eea/volto-eea-map/commit/c0f6dddadaaf28de5b9914214acf735a166fae88)
11
33
  - stylelint [`511845a`](https://github.com/eea/volto-eea-map/commit/511845a319137dfe4e1c0c9846ee5ccb7656b32b)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-map",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
4
4
  "description": "@eeacms/volto-eea-map: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -24,6 +24,7 @@
24
24
  "@eeacms/volto-embed": "^2.0.1",
25
25
  "@plone/scripts": "*",
26
26
  "esri-loader": "3.5.0",
27
+ "react-querybuilder": "4.2.3",
27
28
  "volto-slate": "*"
28
29
  },
29
30
  "devDependencies": {
@@ -7,26 +7,20 @@ export const Schema = (props) => {
7
7
  title: 'Default',
8
8
  fields: [
9
9
  'map_data',
10
- //...(use_visualization ? ['vis_url'] : ['map_data']),
11
10
  'description',
12
11
  'height',
13
12
  'show_legend',
14
13
  'show_download',
15
14
  'show_viewer',
15
+ 'show_sources',
16
16
  ],
17
17
  },
18
18
  ],
19
19
  properties: {
20
- // use_visualization: {
21
- // title: 'Use visualization',
22
- // description:
23
- // 'This setting will enable importing the map data from a visualization. If is enabled, editing the map manually will not be possible',
24
- // type: 'boolean',
25
- // },
26
- // vis_url: {
27
- // widget: 'object_by_path',
28
- // title: 'Visualization',
29
- // },
20
+ show_sources: {
21
+ title: 'Show sources',
22
+ type: 'boolean',
23
+ },
30
24
  height: {
31
25
  title: 'Height',
32
26
  type: 'number',
@@ -1,21 +1,43 @@
1
1
  import React from 'react';
2
2
 
3
+ import { connect } from 'react-redux';
4
+ import { compose } from 'redux';
5
+
3
6
  import Webmap from './components/Webmap';
4
7
  import ExtraViews from './components/widgets/ExtraViews';
5
8
  import { PrivacyProtection } from '@eeacms/volto-embed';
9
+ import { getContent } from '@plone/volto/actions';
6
10
 
7
11
  const View = (props) => {
8
- const { data, id } = props || {};
12
+ const { data, id, path, data_provenance } = props || {};
9
13
  const { map_data = {}, height = '' } = data;
10
14
 
15
+ React.useEffect(() => {
16
+ // get content from document
17
+ if (path) {
18
+ props.getContent(path, null, id);
19
+ }
20
+ // eslint-disable-next-line react-hooks/exhaustive-deps
21
+ }, [path]);
22
+
11
23
  return (
12
24
  <div>
13
25
  <PrivacyProtection data={data} {...props}>
14
26
  <Webmap data={map_data} height={height} id={id} />
15
- <ExtraViews data={data} />
27
+ <ExtraViews data={{ ...data, data_provenance }} />
16
28
  </PrivacyProtection>
17
29
  </div>
18
30
  );
19
31
  };
20
32
 
21
- export default View;
33
+ export default compose(
34
+ connect(
35
+ (state, props) => ({
36
+ data_provenance:
37
+ state.content.subrequests?.[props.id]?.data?.data_provenance,
38
+ }),
39
+ {
40
+ getContent,
41
+ },
42
+ ),
43
+ )(View);
@@ -2,6 +2,7 @@
2
2
  import React from 'react';
3
3
  import { withDeviceSize } from '@eeacms/volto-eea-map/hocs';
4
4
  import { loadModules } from 'esri-loader';
5
+ import { formatQuery } from 'react-querybuilder';
5
6
 
6
7
  const MODULES = [
7
8
  'esri/Map',
@@ -91,7 +92,6 @@ const Webmap = (props) => {
91
92
  .filter(({ map_service_url, layer }) => map_service_url && layer)
92
93
  .map(({ map_service_url, layer, query = '' }) => {
93
94
  const url = `${map_service_url}/${layer}`;
94
-
95
95
  let mapLayer;
96
96
  switch (layer.type) {
97
97
  case 'Raster Layer':
@@ -102,7 +102,7 @@ const Webmap = (props) => {
102
102
  case 'Feature Layer':
103
103
  mapLayer = new FeatureLayer({
104
104
  url,
105
- definitionExpression: query,
105
+ definitionExpression: query ? formatQuery(query, 'sql') : '',
106
106
  });
107
107
  break;
108
108
  case 'Group Layer':
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Button } from 'semantic-ui-react';
3
- import { Icon } from '@plone/volto/components';
3
+ import { Icon, UniversalLink } from '@plone/volto/components';
4
4
 
5
5
  import LegendWidget from './LegendWidget';
6
6
  import { serializeNodes } from 'volto-slate/editor/render';
@@ -15,46 +15,69 @@ const ExtraViews = ({ data }) => {
15
15
  show_legend,
16
16
  show_download,
17
17
  show_viewer,
18
+ show_sources,
19
+ data_provenance = {},
18
20
  } = data;
19
-
20
21
  return (
21
22
  <div className="extra-eea-map-content">
22
- {map_data.layers?.map_layers[0] && (show_download || show_viewer) && (
23
- <div
24
- style={{ display: 'flex', justifyContent: 'end', margin: '10px 0' }}
25
- >
26
- {show_download && (
27
- <a
28
- target="_blank"
29
- rel="noreferrer"
30
- href={`${map_data.layers.map_layers[0].map_layer.map_service_url}?f=lyr&v=9.3`}
31
- >
32
- <Button size="tiny">
33
- <Button.Content>
34
- <Icon name={downloadSVG} title="Download" size="25px" />
35
- </Button.Content>
36
- </Button>
37
- </a>
38
- )}
39
- {show_viewer && (
40
- <a
41
- target="_blank"
42
- rel="noreferrer"
43
- href={
44
- `https://www.arcgis.com/home/webmap/viewer.html?url=` +
45
- `${map_data.layers.map_layers[0].map_layer.map_service_url}&source=sd`
46
- }
47
- >
48
- <Button size="tiny">
49
- <Button.Content>
50
- <Icon name={worldSVG} title="View map" size="25px" />
51
- </Button.Content>
52
- </Button>
53
- </a>
23
+ {map_data &&
24
+ map_data.layers?.map_layers[0] &&
25
+ (show_download || show_viewer) && (
26
+ <div
27
+ style={{ display: 'flex', justifyContent: 'end', margin: '10px 0' }}
28
+ >
29
+ {show_download && (
30
+ <a
31
+ target="_blank"
32
+ rel="noreferrer"
33
+ href={`${map_data.layers.map_layers[0].map_layer.map_service_url}?f=lyr&v=9.3`}
34
+ >
35
+ <Button size="tiny">
36
+ <Button.Content>
37
+ <Icon name={downloadSVG} title="Download" size="25px" />
38
+ </Button.Content>
39
+ </Button>
40
+ </a>
41
+ )}
42
+ {show_viewer && (
43
+ <a
44
+ target="_blank"
45
+ rel="noreferrer"
46
+ href={
47
+ `https://www.arcgis.com/home/webmap/viewer.html?url=` +
48
+ `${map_data.layers.map_layers[0].map_layer.map_service_url}&source=sd`
49
+ }
50
+ >
51
+ <Button size="tiny">
52
+ <Button.Content>
53
+ <Icon name={worldSVG} title="View map" size="25px" />
54
+ </Button.Content>
55
+ </Button>
56
+ </a>
57
+ )}
58
+ </div>
59
+ )}
60
+ {show_legend && map_data && <LegendWidget data={map_data} />}
61
+ {show_sources && (
62
+ <>
63
+ {data_provenance &&
64
+ data_provenance.data &&
65
+ data_provenance.data.length > 0 ? (
66
+ <div>
67
+ <h3>Sources:</h3>
68
+ {data_provenance.data.map((data, i) => (
69
+ <div key={i}>
70
+ <p className="map_source_title">{data.title}</p>
71
+ <p className="map_source_description">{data.organisation}</p>
72
+ <UniversalLink href={data.link}>{data.link} </UniversalLink>
73
+ </div>
74
+ ))}
75
+ </div>
76
+ ) : (
77
+ <p>Data provenance is not set for visualization used or page</p>
54
78
  )}
55
- </div>
79
+ </>
56
80
  )}
57
- {show_legend && <LegendWidget data={map_data} />}
58
81
  {description && serializeNodes(description)}
59
82
  </div>
60
83
  );
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  import { Icon } from '@plone/volto/components';
3
3
  import { Input, Select, Button, Grid } from 'semantic-ui-react';
4
+ import { QueryBuilder } from 'react-querybuilder';
5
+ import 'react-querybuilder/dist/query-builder.css';
4
6
 
5
7
  import checkSVG from '@plone/volto/icons/check.svg';
6
8
  import closeSVG from '@plone/volto/icons/clear.svg';
@@ -31,7 +33,8 @@ const LayerSelectWidget = (props) => {
31
33
  const [availableLayers, setAvailableLayers] = React.useState(
32
34
  available_layers,
33
35
  );
34
- const [layerQuery, setLayerQuery] = React.useState(query);
36
+
37
+ const [builtQuery, setBuiltQuery] = React.useState(query);
35
38
 
36
39
  const handleServiceUrlCheck = async () => {
37
40
  // fetch url, save it, populate layers options
@@ -79,14 +82,16 @@ const LayerSelectWidget = (props) => {
79
82
  map_data: mapData,
80
83
  query: '',
81
84
  });
82
- setLayerQuery('');
85
+ setBuiltQuery('');
83
86
  };
84
87
 
85
88
  const handleQueryLayer = () => {
86
- onChange(id, {
87
- ...value,
88
- query: layerQuery,
89
- });
89
+ if (builtQuery) {
90
+ onChange(id, {
91
+ ...value,
92
+ query: builtQuery,
93
+ });
94
+ }
90
95
  };
91
96
 
92
97
  const handleChangeServiceUrl = (value) => {
@@ -94,7 +99,7 @@ const LayerSelectWidget = (props) => {
94
99
  setCheckColor('');
95
100
  setServiceUrl(value);
96
101
  setAvailableLayers('');
97
- setLayerQuery('');
102
+ setBuiltQuery('');
98
103
  setSelectedLayer('');
99
104
  };
100
105
 
@@ -103,7 +108,7 @@ const LayerSelectWidget = (props) => {
103
108
  setServiceUrl(map_service_url);
104
109
  setCheckColor('');
105
110
  setAvailableLayers(available_layers);
106
- setLayerQuery(query);
111
+ setBuiltQuery('');
107
112
  setSelectedLayer(layer);
108
113
  };
109
114
 
@@ -178,15 +183,17 @@ const LayerSelectWidget = (props) => {
178
183
  <h5 style={{ padding: '0', margin: '15px 0px 5px 0px' }}>
179
184
  Query Layer
180
185
  </h5>
181
- <Grid.Row stretched>
182
- <Input
183
- type="text"
184
- style={{ width: '100%' }}
185
- onChange={(e, { value }) => setLayerQuery(value)}
186
- value={layerQuery}
187
- ></Input>
186
+ <Grid.Row>
187
+ <QueryBuilder
188
+ fields={fields.map((fi, i) => {
189
+ return { name: fi.name, label: fi.name };
190
+ })}
191
+ query={builtQuery}
192
+ onQueryChange={(q) => setBuiltQuery(q)}
193
+ enableDragAndDrop={false}
194
+ />
188
195
  </Grid.Row>
189
- {layerQuery && (
196
+ {builtQuery && (
190
197
  <Grid.Row>
191
198
  <Button
192
199
  type="submit"
@@ -213,7 +220,7 @@ const LayerSelectWidget = (props) => {
213
220
  </Grid.Row>
214
221
  {fields.map((field, id) => (
215
222
  <p style={{ fontSize: '12px', padding: '0 5px' }}>
216
- {field.alias}
223
+ <strong>{field.alias}</strong> ({field.type})
217
224
  </p>
218
225
  ))}
219
226
  </>
@@ -6,16 +6,33 @@ import { Icon } from '@plone/volto/components';
6
6
  import rightKeySVG from '@plone/volto/icons/right-key.svg';
7
7
  import downKeySVG from '@plone/volto/icons/down-key.svg';
8
8
 
9
- const LayerLegend = ({ data, name }) => {
9
+ const LayerLegend = ({ data }) => {
10
10
  const [expand, setExpand] = React.useState(true);
11
+ const [legendRows, setLegendRows] = React.useState([]);
11
12
 
13
+ const { id, name } = data.layer || {};
14
+
15
+ const fetchLegend = async (url, activeLayerID) => {
16
+ let legendData = await fetchArcgisData(url);
17
+
18
+ const { layers = [] } = legendData;
19
+ const selectedLayer = layers.filter((l, i) => l.layerId === activeLayerID);
20
+ setLegendRows(selectedLayer[0].legend);
21
+ };
22
+
23
+ React.useEffect(() => {
24
+ if (data.map_service_url && id !== undefined) {
25
+ fetchLegend(`${data.map_service_url}/legend`, id);
26
+ }
27
+ // eslint-disable-next-line react-hooks/exhaustive-deps
28
+ }, [id, data.map_service_url]);
12
29
  return (
13
30
  <div style={{ display: 'flex', flexDirection: 'column' }}>
14
31
  <h5
15
32
  role="presentation"
16
33
  onClick={() => setExpand(!expand)}
17
34
  style={{
18
- marginTop: '10px',
35
+ marginTop: '15px',
19
36
  marginBottom: '5px',
20
37
  cursor: 'pointer',
21
38
  display: 'flex',
@@ -30,8 +47,8 @@ const LayerLegend = ({ data, name }) => {
30
47
  </h5>
31
48
  <div style={{ display: 'flex', flexDirection: 'column' }}>
32
49
  {expand &&
33
- data.length > 0 &&
34
- data.map((item, i) => {
50
+ legendRows.length > 0 &&
51
+ legendRows.map((item, i) => {
35
52
  return (
36
53
  <span key={i} style={{ display: 'flex', alignItems: 'center' }}>
37
54
  <img
@@ -53,49 +70,25 @@ const LegendWidget = (props) => {
53
70
  const data = React.useMemo(() => props.data, [props.data]);
54
71
  const { layers = {} } = data;
55
72
  const { map_layers = [] } = layers;
56
- const [legendLayers, setLegendLayers] = React.useState([]);
57
-
58
- const activeLayer = map_layers.length > 0 ? map_layers[0]?.map_layer : '';
59
73
 
60
- const fetchLegend = async (url) => {
61
- let legendData = await fetchArcgisData(url);
62
-
63
- //TODO: configure this for multiple layers
64
- const { layers = [] } = legendData;
65
- const selectedLayerLedend = layers.filter(
66
- (l, i) => l.layerId === activeLayer.layer.id,
67
- );
68
- setLegendLayers(selectedLayerLedend);
69
- };
70
-
71
- React.useEffect(() => {
72
- if (activeLayer) {
73
- fetchLegend(`${activeLayer.map_service_url}/legend`);
74
- }
75
- // eslint-disable-next-line react-hooks/exhaustive-deps
76
- }, [data, activeLayer.map_service_url]);
77
74
  return (
78
75
  <>
79
76
  <div style={{ margin: '10px 0' }}>
80
77
  <Grid>
81
78
  <Grid.Row>
82
79
  <Grid.Column>
83
- <h3>Legend:</h3>
84
- {!activeLayer && (
80
+ <h4>Legend:</h4>
81
+ {map_layers.length === 0 && (
85
82
  <p>
86
83
  No layer found for legend. Please add a map layer from editor.
87
84
  </p>
88
85
  )}
89
- {legendLayers.length > 0 &&
90
- legendLayers.map((layer, i) => {
91
- return (
92
- <LayerLegend
93
- key={i}
94
- name={layer?.layerName}
95
- data={layer?.legend}
96
- />
97
- );
98
- })}
86
+
87
+ {map_layers &&
88
+ map_layers.length > 0 &&
89
+ map_layers.map((l, i) => (
90
+ <LayerLegend key={i} data={l.map_layer} />
91
+ ))}
99
92
  </Grid.Column>
100
93
  </Grid.Row>
101
94
  </Grid>
@@ -62,7 +62,6 @@ const MapEditorWidget = (props) => {
62
62
  >
63
63
  <InlineForm
64
64
  block={block}
65
- //title={schema.title}
66
65
  schema={schema}
67
66
  onChangeField={(id, value) => {
68
67
  handleChangeField(id, value);
@@ -53,6 +53,68 @@
53
53
  animation: fadeDown 0.2s ease-in;
54
54
  }
55
55
 
56
+ .ruleGroup {
57
+ border: 1px solid lightgray !important;
58
+ margin: 10px 0;
59
+ background-color: transparent !important;
60
+ }
61
+
62
+ .ruleGroup-addRule {
63
+ padding: 7px 10px !important;
64
+ background-color: darkgray !important;
65
+ border-radius: 5px;
66
+ color: white;
67
+ cursor: pointer;
68
+ font-size: 12px;
69
+ font-weight: bold;
70
+ }
71
+
72
+ .ruleGroup-addGroup {
73
+ padding: 7px 10px !important;
74
+ background-color: darkgray !important;
75
+ border-radius: 5px;
76
+ color: white;
77
+ cursor: pointer;
78
+ font-size: 12px;
79
+ font-weight: bold;
80
+ }
81
+
82
+ .ruleGroup-combinators {
83
+ padding: 0.3em 0 !important;
84
+ }
85
+
86
+ .rule-operators {
87
+ padding: 0.3em 0 !important;
88
+ }
89
+
90
+ .rule-value {
91
+ padding: 0.3em 0 !important;
92
+ }
93
+
94
+ .rule-fields {
95
+ padding: 0.3em 0 !important;
96
+ }
97
+
98
+ .rule-remove {
99
+ padding: 1px 6px !important;
100
+ border: 2px solid #d02144 !important;
101
+ border-radius: 50px;
102
+ color: #d02144;
103
+ cursor: pointer;
104
+ }
105
+
106
+ .map_source_title {
107
+ margin-top: 15px !important;
108
+ margin-bottom: 5px !important;
109
+ font-size: 18px;
110
+ font-weight: bold;
111
+ }
112
+
113
+ .map_source_description {
114
+ margin: 5px 0 !important;
115
+ font-size: 14px;
116
+ }
117
+
56
118
  @keyframes fadeDown {
57
119
  from {
58
120
  opacity: 0;
@@ -7,12 +7,12 @@ import { addPrivacyProtectionToSchema } from '@eeacms/volto-embed';
7
7
  import './styles/map.css';
8
8
 
9
9
  const Edit = (props) => {
10
- const { block, data, onChangeBlock, selected } = props;
10
+ const { block, data, onChangeBlock, selected, id } = props;
11
11
  const schema = React.useMemo(() => Schema(props), [props]);
12
12
 
13
13
  return (
14
14
  <div>
15
- <View data={data} />
15
+ <View data={data} id={id} />
16
16
  <SidebarPortal selected={selected}>
17
17
  <BlockDataForm
18
18
  block={block}
@@ -12,6 +12,7 @@ export const Schema = (props) => {
12
12
  'show_legend',
13
13
  'show_download',
14
14
  'show_viewer',
15
+ 'show_sources',
15
16
  ],
16
17
  },
17
18
  ],
@@ -28,6 +29,10 @@ export const Schema = (props) => {
28
29
  title: 'Description',
29
30
  widget: 'slate',
30
31
  },
32
+ show_sources: {
33
+ title: 'Show sources',
34
+ type: 'boolean',
35
+ },
31
36
  show_legend: {
32
37
  title: 'Show legend',
33
38
  type: 'boolean',
@@ -6,15 +6,18 @@ import { compose } from 'redux';
6
6
  import { PrivacyProtection } from '@eeacms/volto-embed';
7
7
  import Webmap from '../EEAMap/components/Webmap';
8
8
  import ExtraViews from '../EEAMap/components/widgets/ExtraViews';
9
- import { getVisualization } from '@eeacms/volto-eea-map/actions';
9
+ import { getContent } from '@plone/volto/actions';
10
10
 
11
11
  const View = (props) => {
12
- const { data, map_visualization } = props || {};
12
+ const { data, viz_content = {}, id } = props || {};
13
13
  const { height = '', vis_url = '' } = data;
14
14
 
15
+ const { map_visualization_data = '', data_provenance = {} } =
16
+ viz_content || {};
17
+
15
18
  React.useEffect(() => {
16
19
  if (vis_url) {
17
- props.getVisualization(vis_url);
20
+ props.getContent(vis_url, null, id);
18
21
  }
19
22
  // eslint-disable-next-line react-hooks/exhaustive-deps
20
23
  }, [vis_url]);
@@ -22,13 +25,19 @@ const View = (props) => {
22
25
  return (
23
26
  <div>
24
27
  <PrivacyProtection data={data} {...props}>
25
- {map_visualization && (
28
+ {map_visualization_data && (
26
29
  <div>
27
- <Webmap data={map_visualization.data} height={height} />
28
- <ExtraViews data={{ ...data, map_data: map_visualization.data }} />
30
+ <Webmap data={map_visualization_data} height={height} />
31
+ <ExtraViews
32
+ data={{
33
+ ...data,
34
+ data_provenance,
35
+ map_data: map_visualization_data,
36
+ }}
37
+ />
29
38
  </div>
30
39
  )}
31
- {!map_visualization && (
40
+ {!map_visualization_data && (
32
41
  <p>No map view to show. Set visualization in block configuration.</p>
33
42
  )}
34
43
  </PrivacyProtection>
@@ -39,10 +48,10 @@ const View = (props) => {
39
48
  export default compose(
40
49
  connect(
41
50
  (state, props) => ({
42
- map_visualization: state.map_visualizations.data[props.data.vis_url],
51
+ viz_content: state.content.subrequests?.[props.id]?.data,
43
52
  }),
44
53
  {
45
- getVisualization,
54
+ getContent,
46
55
  },
47
56
  ),
48
57
  )(View);