@eeacms/volto-cca-policy 0.2.57 → 0.2.59
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 +24 -1
- package/package.json +2 -1
- package/src/components/manage/Blocks/CollectionStatistics/CollectionStatsView.jsx +1 -1
- package/src/components/theme/Views/BrokenLinks.jsx +284 -0
- package/src/components/theme/Views/BrokenLinks.test.jsx +30 -0
- package/src/components/theme/Views/brokenlinks.less +21 -0
- package/src/components/theme/Widgets/GeolocationWidget.jsx +23 -4
- package/src/components/theme/Widgets/GeolocationWidgetMapContainer.jsx +71 -6
- package/src/components/theme/Widgets/geolocation.css +3 -0
- package/src/index.js +17 -5
- package/theme/elements/list.overrides +3 -2
- package/theme/globals/site.overrides +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +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.2.
|
|
7
|
+
### [0.2.59](https://github.com/eea/volto-cca-policy/compare/0.2.58...0.2.59) - 12 August 2024
|
|
8
|
+
|
|
9
|
+
#### :hammer_and_wrench: Others
|
|
10
|
+
|
|
11
|
+
- Don't hardcode to last 5 years [Tiberiu Ichim - [`475d7e1`](https://github.com/eea/volto-cca-policy/commit/475d7e17a8faeec9115298bd38f30db51b038a36)]
|
|
12
|
+
### [0.2.58](https://github.com/eea/volto-cca-policy/compare/0.2.57...0.2.58) - 12 August 2024
|
|
13
|
+
|
|
14
|
+
#### :house: Internal changes
|
|
15
|
+
|
|
16
|
+
- style: Automated code fix [eea-jenkins - [`30f4774`](https://github.com/eea/volto-cca-policy/commit/30f4774d7d21cb5bbb4f2782a42c0223fde00e90)]
|
|
17
|
+
|
|
18
|
+
#### :hammer_and_wrench: Others
|
|
19
|
+
|
|
20
|
+
- Improve css for broken links [Tiberiu Ichim - [`ef17b71`](https://github.com/eea/volto-cca-policy/commit/ef17b71c4ae6c19202e110f8baca0055d05ad670)]
|
|
21
|
+
- Update GeolocationWidgetMapContainer.jsx [Tiberiu Ichim - [`3da08d2`](https://github.com/eea/volto-cca-policy/commit/3da08d2476bb73a792843d4bc20b3219cb32f65f)]
|
|
22
|
+
- Add test [Tiberiu Ichim - [`751ed81`](https://github.com/eea/volto-cca-policy/commit/751ed81a26de0fde987ab51fcb0bb70ceaeea600)]
|
|
23
|
+
- Stylint fix [Tiberiu Ichim - [`303bb86`](https://github.com/eea/volto-cca-policy/commit/303bb865cdcbb34af23e6f635a0bc9eb41782ef9)]
|
|
24
|
+
- Add start-backend target [Tiberiu Ichim - [`2dbf5df`](https://github.com/eea/volto-cca-policy/commit/2dbf5df311347350f8c42ee3daf3dbd2a840c85a)]
|
|
25
|
+
- No console.log [Tiberiu Ichim - [`06125f9`](https://github.com/eea/volto-cca-policy/commit/06125f9e65de369caacf71a00a8c77d7bc0202b3)]
|
|
26
|
+
- Fixes [Tiberiu Ichim - [`4f00318`](https://github.com/eea/volto-cca-policy/commit/4f00318c0bbb1aea68348cf7efa5684ed1b68fef)]
|
|
27
|
+
- Add filters and sorting [Tiberiu Ichim - [`f6aa329`](https://github.com/eea/volto-cca-policy/commit/f6aa329610180b6e72e879bc38c2c51ed5e65d6c)]
|
|
28
|
+
- Use react-table [Tiberiu Ichim - [`9d6af3a`](https://github.com/eea/volto-cca-policy/commit/9d6af3a0dfcf99bf52c6619ef14f62a047aa0b73)]
|
|
29
|
+
- Add brokenlinks component [Tiberiu Ichim - [`d9a6230`](https://github.com/eea/volto-cca-policy/commit/d9a6230f94021ee2900830284ef2fda8563fe5d0)]
|
|
30
|
+
### [0.2.57](https://github.com/eea/volto-cca-policy/compare/0.2.56...0.2.57) - 23 July 2024
|
|
8
31
|
|
|
9
32
|
### [0.2.56](https://github.com/eea/volto-cca-policy/compare/0.2.55...0.2.56) - 22 July 2024
|
|
10
33
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eeacms/volto-cca-policy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.59",
|
|
4
4
|
"description": "@eeacms/volto-cca-policy: Volto add-on",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"author": "European Environment Agency: IDM2 A-Team",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@eeacms/volto-slate-label": "^0.6.0",
|
|
40
40
|
"@eeacms/volto-tabs-block": "^7.5.1",
|
|
41
41
|
"@elastic/search-ui": "1.21.2",
|
|
42
|
+
"@tanstack/react-table": "8.19.3",
|
|
42
43
|
"d3-array": "^2.12.1",
|
|
43
44
|
"jotai": "^1.6.0",
|
|
44
45
|
"query-string": "7.1.0",
|
|
@@ -69,7 +69,7 @@ const makeSearchBlockQuery = ({ base, query, field, value }) => {
|
|
|
69
69
|
const makeEEASearchQuery = ({ base, field, value, extraFilters }) => {
|
|
70
70
|
// TODO: don't hardcode the language
|
|
71
71
|
const allFields = [
|
|
72
|
-
['issued.date', 'Last 5 years'],
|
|
72
|
+
// ['issued.date', 'Last 5 years'],
|
|
73
73
|
['language', 'en'],
|
|
74
74
|
[field, value],
|
|
75
75
|
...(extraFilters?.map(({ id, value }) => [id, value]) || []),
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { expandToBackendURL } from '@plone/volto/helpers';
|
|
2
|
+
import { injectLazyLibs } from '@plone/volto/helpers/Loadable';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Button,
|
|
6
|
+
Table,
|
|
7
|
+
TableBody,
|
|
8
|
+
TableCell,
|
|
9
|
+
TableHeader,
|
|
10
|
+
TableHeaderCell,
|
|
11
|
+
TableRow,
|
|
12
|
+
} from 'semantic-ui-react';
|
|
13
|
+
|
|
14
|
+
import './brokenlinks.less';
|
|
15
|
+
|
|
16
|
+
function Filter({ column }) {
|
|
17
|
+
const columnFilterValue = column.getFilterValue();
|
|
18
|
+
const { filterVariant } = column.columnDef.meta ?? {};
|
|
19
|
+
const handleRangeNumberChangeMin = React.useCallback(
|
|
20
|
+
(value) => {
|
|
21
|
+
column.setFilterValue((old) => [value, old?.[1]]);
|
|
22
|
+
},
|
|
23
|
+
[column],
|
|
24
|
+
);
|
|
25
|
+
const handleRangeNumberChangeMax = React.useCallback(
|
|
26
|
+
(value) => column.setFilterValue((old) => [old?.[0], value]),
|
|
27
|
+
[column],
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const handleTextChange = React.useCallback(
|
|
31
|
+
(value) => column.setFilterValue(value),
|
|
32
|
+
[column],
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return filterVariant === 'range' ? (
|
|
36
|
+
<div>
|
|
37
|
+
<div className="flex space-x-2">
|
|
38
|
+
{/* See faceted column filters example for min max values functionality */}
|
|
39
|
+
<DebouncedInput
|
|
40
|
+
type="number"
|
|
41
|
+
value={columnFilterValue?.[0] ?? ''}
|
|
42
|
+
onChange={handleRangeNumberChangeMin}
|
|
43
|
+
placeholder={`Min`}
|
|
44
|
+
className="w-24 border shadow rounded"
|
|
45
|
+
/>
|
|
46
|
+
<DebouncedInput
|
|
47
|
+
type="number"
|
|
48
|
+
value={columnFilterValue?.[1] ?? ''}
|
|
49
|
+
onChange={handleRangeNumberChangeMax}
|
|
50
|
+
placeholder={`Max`}
|
|
51
|
+
className="w-24 border shadow rounded"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div className="h-1" />
|
|
55
|
+
</div>
|
|
56
|
+
) : filterVariant === 'select' ? (
|
|
57
|
+
<select
|
|
58
|
+
onBlur={(e) => column.setFilterValue(e.target.value)}
|
|
59
|
+
onChange={(e) => column.setFilterValue(e.target.value)}
|
|
60
|
+
value={columnFilterValue?.toString()}
|
|
61
|
+
>
|
|
62
|
+
{/* See faceted column filters example for dynamic select options */}
|
|
63
|
+
<option value="">All</option>
|
|
64
|
+
<option value="complicated">complicated</option>
|
|
65
|
+
<option value="relationship">relationship</option>
|
|
66
|
+
<option value="single">single</option>
|
|
67
|
+
</select>
|
|
68
|
+
) : (
|
|
69
|
+
<DebouncedInput
|
|
70
|
+
className="w-36 border shadow rounded"
|
|
71
|
+
onChange={handleTextChange}
|
|
72
|
+
placeholder={`Search...`}
|
|
73
|
+
type="text"
|
|
74
|
+
value={columnFilterValue ?? ''}
|
|
75
|
+
/>
|
|
76
|
+
// See faceted column filters example for datalist search suggestions
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// A typical debounced input react component
|
|
81
|
+
function DebouncedInput({
|
|
82
|
+
value: initialValue,
|
|
83
|
+
onChange,
|
|
84
|
+
debounce = 500,
|
|
85
|
+
...props
|
|
86
|
+
}) {
|
|
87
|
+
const [value, setValue] = React.useState(initialValue);
|
|
88
|
+
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
setValue(initialValue);
|
|
91
|
+
}, [initialValue]);
|
|
92
|
+
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
const timeout = setTimeout(() => {
|
|
95
|
+
onChange(value);
|
|
96
|
+
}, debounce);
|
|
97
|
+
|
|
98
|
+
return () => clearTimeout(timeout);
|
|
99
|
+
}, [value, debounce, onChange]);
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<input
|
|
103
|
+
{...props}
|
|
104
|
+
value={value}
|
|
105
|
+
onChange={(e) => setValue(e.target.value)}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function BrokenLinksComponent({ reactTable }) {
|
|
111
|
+
const { createColumnHelper } = reactTable;
|
|
112
|
+
|
|
113
|
+
const [results, setResults] = React.useState({});
|
|
114
|
+
|
|
115
|
+
React.useEffect(() => {
|
|
116
|
+
const url = expandToBackendURL('/@broken_links');
|
|
117
|
+
let isMounted = true;
|
|
118
|
+
async function handler() {
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(url);
|
|
121
|
+
const results = await response.json();
|
|
122
|
+
const data = Array.from(Object.values(results.broken_links));
|
|
123
|
+
if (isMounted) setResults(data);
|
|
124
|
+
} catch {
|
|
125
|
+
// eslint-disable-next-line no-console
|
|
126
|
+
console.error('Error in fetching broken links');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
handler();
|
|
130
|
+
return () => (isMounted = false);
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
const columnHelper = createColumnHelper();
|
|
134
|
+
const columns = React.useMemo(
|
|
135
|
+
() => [
|
|
136
|
+
columnHelper.accessor('url', {
|
|
137
|
+
header: () => 'URL',
|
|
138
|
+
cell: (info) => <a href={info.getValue()}>{info.getValue()}</a>,
|
|
139
|
+
filterFn: 'includesString',
|
|
140
|
+
}),
|
|
141
|
+
columnHelper.accessor('status', {
|
|
142
|
+
header: () => 'Status',
|
|
143
|
+
filterFn: 'includesString',
|
|
144
|
+
}),
|
|
145
|
+
columnHelper.accessor('date', {
|
|
146
|
+
header: () => 'Last checked',
|
|
147
|
+
filterFn: 'includesString',
|
|
148
|
+
}),
|
|
149
|
+
|
|
150
|
+
columnHelper.accessor('object_url', {
|
|
151
|
+
header: () => 'Reference from',
|
|
152
|
+
cell: (info) => <a href={info.getValue()}>{info.getValue()}</a>,
|
|
153
|
+
filterFn: 'includesString',
|
|
154
|
+
}),
|
|
155
|
+
],
|
|
156
|
+
[columnHelper],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<FilteredTable data={results} columns={columns} reactTable={reactTable} />
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function FilteredTable({ reactTable, data, columns }) {
|
|
165
|
+
const {
|
|
166
|
+
useReactTable,
|
|
167
|
+
getPaginationRowModel,
|
|
168
|
+
getCoreRowModel,
|
|
169
|
+
flexRender,
|
|
170
|
+
getFilteredRowModel,
|
|
171
|
+
getSortedRowModel,
|
|
172
|
+
} = reactTable;
|
|
173
|
+
|
|
174
|
+
const table = useReactTable({
|
|
175
|
+
data,
|
|
176
|
+
columns,
|
|
177
|
+
getCoreRowModel: getCoreRowModel(),
|
|
178
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
179
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
180
|
+
getSortedRowModel: getSortedRowModel(),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div className="broken-links-table ui container">
|
|
185
|
+
<Table>
|
|
186
|
+
<TableHeader>
|
|
187
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
188
|
+
<TableRow key={headerGroup.id}>
|
|
189
|
+
{headerGroup.headers.map((header) => (
|
|
190
|
+
<TableHeaderCell key={header.id} className={header.id}>
|
|
191
|
+
{header.isPlaceholder ? null : (
|
|
192
|
+
<>
|
|
193
|
+
<div
|
|
194
|
+
tabIndex="-1"
|
|
195
|
+
role="button"
|
|
196
|
+
onKeyDown={header.column.getToggleSortingHandler()}
|
|
197
|
+
{...{
|
|
198
|
+
className: header.column.getCanSort()
|
|
199
|
+
? 'cursor-pointer select-none'
|
|
200
|
+
: '',
|
|
201
|
+
onClick: header.column.getToggleSortingHandler(),
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
{flexRender(
|
|
205
|
+
header.column.columnDef.header,
|
|
206
|
+
header.getContext(),
|
|
207
|
+
)}
|
|
208
|
+
{{
|
|
209
|
+
asc: ' 🔼',
|
|
210
|
+
desc: ' 🔽',
|
|
211
|
+
}[header.column.getIsSorted()] ?? null}
|
|
212
|
+
</div>
|
|
213
|
+
{header.column.getCanFilter() ? (
|
|
214
|
+
<div>
|
|
215
|
+
<Filter column={header.column} />
|
|
216
|
+
</div>
|
|
217
|
+
) : null}
|
|
218
|
+
</>
|
|
219
|
+
)}
|
|
220
|
+
</TableHeaderCell>
|
|
221
|
+
))}
|
|
222
|
+
</TableRow>
|
|
223
|
+
))}
|
|
224
|
+
</TableHeader>
|
|
225
|
+
<TableBody>
|
|
226
|
+
{table.getRowModel().rows.map((row) => {
|
|
227
|
+
return (
|
|
228
|
+
<TableRow key={row.id}>
|
|
229
|
+
{row.getVisibleCells().map((cell) => (
|
|
230
|
+
<TableCell key={cell.id}>
|
|
231
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
232
|
+
</TableCell>
|
|
233
|
+
))}
|
|
234
|
+
</TableRow>
|
|
235
|
+
);
|
|
236
|
+
})}
|
|
237
|
+
</TableBody>
|
|
238
|
+
</Table>
|
|
239
|
+
<div className="pagination">
|
|
240
|
+
<Button
|
|
241
|
+
onClick={() => table.firstPage()}
|
|
242
|
+
disabled={!table.getCanPreviousPage()}
|
|
243
|
+
>
|
|
244
|
+
{'<<'}
|
|
245
|
+
</Button>
|
|
246
|
+
<Button
|
|
247
|
+
onClick={() => table.previousPage()}
|
|
248
|
+
disabled={!table.getCanPreviousPage()}
|
|
249
|
+
>
|
|
250
|
+
{'<'}
|
|
251
|
+
</Button>
|
|
252
|
+
<Button
|
|
253
|
+
onClick={() => table.nextPage()}
|
|
254
|
+
disabled={!table.getCanNextPage()}
|
|
255
|
+
>
|
|
256
|
+
{'>'}
|
|
257
|
+
</Button>
|
|
258
|
+
<Button
|
|
259
|
+
onClick={() => table.lastPage()}
|
|
260
|
+
disabled={!table.getCanNextPage()}
|
|
261
|
+
>
|
|
262
|
+
{'>>'}
|
|
263
|
+
</Button>
|
|
264
|
+
<select
|
|
265
|
+
value={table.getState().pagination.pageSize}
|
|
266
|
+
onChange={(e) => {
|
|
267
|
+
table.setPageSize(Number(e.target.value));
|
|
268
|
+
}}
|
|
269
|
+
onBlur={(e) => {
|
|
270
|
+
table.setPageSize(Number(e.target.value));
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
{[10, 20, 30, 40, 50].map((pageSize) => (
|
|
274
|
+
<option key={pageSize} value={pageSize}>
|
|
275
|
+
{pageSize}
|
|
276
|
+
</option>
|
|
277
|
+
))}
|
|
278
|
+
</select>
|
|
279
|
+
</div>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default injectLazyLibs(['reactTable'])(BrokenLinksComponent);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import configureStore from 'redux-mock-store';
|
|
4
|
+
import '@testing-library/jest-dom/extend-expect';
|
|
5
|
+
import { render } from '@testing-library/react';
|
|
6
|
+
import { Provider } from 'react-intl-redux';
|
|
7
|
+
import { BrokenLinksComponent } from './BrokenLinks';
|
|
8
|
+
|
|
9
|
+
const mockStore = configureStore();
|
|
10
|
+
|
|
11
|
+
describe('BrokenLinksComponent', () => {
|
|
12
|
+
it('should render the component', async () => {
|
|
13
|
+
const store = mockStore({
|
|
14
|
+
userSession: { token: '1234' },
|
|
15
|
+
intl: {
|
|
16
|
+
locale: 'en',
|
|
17
|
+
messages: {},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
const reactTable = await import('@tanstack/react-table');
|
|
21
|
+
const { container } = render(
|
|
22
|
+
<Provider store={store}>
|
|
23
|
+
<MemoryRouter>
|
|
24
|
+
<BrokenLinksComponent reactTable={reactTable} />
|
|
25
|
+
</MemoryRouter>
|
|
26
|
+
</Provider>,
|
|
27
|
+
);
|
|
28
|
+
expect(container).toBeTruthy();
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.broken-links-table {
|
|
2
|
+
td {
|
|
3
|
+
max-width: 200px;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
a {
|
|
7
|
+
display: block;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
text-overflow: ellipsis;
|
|
10
|
+
white-space: nowrap;
|
|
11
|
+
// max-width: 200px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.cursor-pointer {
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.select-none {
|
|
19
|
+
user-select: none;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { Input } from 'semantic-ui-react';
|
|
3
|
+
import { Input, Label } from 'semantic-ui-react';
|
|
4
4
|
import config from '@plone/volto/registry';
|
|
5
5
|
|
|
6
6
|
import { injectIntl } from 'react-intl';
|
|
7
7
|
import { FormFieldWrapper } from '@plone/volto/components';
|
|
8
|
-
import MapContainer from '
|
|
8
|
+
import MapContainer from './GeolocationWidgetMapContainer';
|
|
9
|
+
|
|
10
|
+
import './geolocation.css';
|
|
9
11
|
|
|
10
12
|
const defaultValue = {
|
|
11
13
|
latitude: 55.6761,
|
|
@@ -16,6 +18,7 @@ const GeolocationWidget = (props) => {
|
|
|
16
18
|
const { id, value, onChange } = props;
|
|
17
19
|
|
|
18
20
|
const [address, setAddress] = useState('');
|
|
21
|
+
const [isFetching, setIsFetching] = useState();
|
|
19
22
|
|
|
20
23
|
const handleAddressChange = (event) => {
|
|
21
24
|
setAddress(event.target.value);
|
|
@@ -34,9 +37,11 @@ const GeolocationWidget = (props) => {
|
|
|
34
37
|
const path = `${base}${corsProxyPath}/${url}`;
|
|
35
38
|
|
|
36
39
|
let locations;
|
|
40
|
+
setIsFetching(true);
|
|
37
41
|
try {
|
|
38
42
|
const response = await fetch(path);
|
|
39
43
|
locations = await response.json();
|
|
44
|
+
setIsFetching(false);
|
|
40
45
|
} catch (e) {
|
|
41
46
|
// eslint-disable-next-line no-console
|
|
42
47
|
console.log('error in fetching location', e);
|
|
@@ -59,10 +64,19 @@ const GeolocationWidget = (props) => {
|
|
|
59
64
|
<div className="ui form">
|
|
60
65
|
<div className="inline fields">
|
|
61
66
|
<div className="field">
|
|
62
|
-
<Input
|
|
67
|
+
<Input
|
|
68
|
+
type="text"
|
|
69
|
+
value={address}
|
|
70
|
+
onChange={handleAddressChange}
|
|
71
|
+
onKeyDown={(e) => {
|
|
72
|
+
if (e.key === 'Enter') handleSearch(e);
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
63
75
|
</div>
|
|
64
76
|
<div className="field">
|
|
65
|
-
<button onClick={handleSearch}>
|
|
77
|
+
<button onClick={handleSearch}>
|
|
78
|
+
{isFetching ? 'Loading' : 'Search'}
|
|
79
|
+
</button>
|
|
66
80
|
</div>
|
|
67
81
|
</div>
|
|
68
82
|
</div>
|
|
@@ -70,10 +84,14 @@ const GeolocationWidget = (props) => {
|
|
|
70
84
|
key={mapKey}
|
|
71
85
|
longitude={value?.longitude || defaultValue.longitude}
|
|
72
86
|
latitude={value?.latitude || defaultValue.latitude}
|
|
87
|
+
onChange={({ latitude, longitude }) =>
|
|
88
|
+
onChange(id, { ...value, longitude, latitude })
|
|
89
|
+
}
|
|
73
90
|
/>
|
|
74
91
|
<div className="ui form">
|
|
75
92
|
<div className="inline fields">
|
|
76
93
|
<div className="field">
|
|
94
|
+
<Label>Latitude</Label>
|
|
77
95
|
<Input
|
|
78
96
|
type="number"
|
|
79
97
|
placeholder="latitude"
|
|
@@ -84,6 +102,7 @@ const GeolocationWidget = (props) => {
|
|
|
84
102
|
/>
|
|
85
103
|
</div>
|
|
86
104
|
<div className="field">
|
|
105
|
+
<Label>Longitude</Label>
|
|
87
106
|
<Input
|
|
88
107
|
type="number"
|
|
89
108
|
placeholder="longitude"
|
|
@@ -1,12 +1,72 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { openlayers as ol } from '@eeacms/volto-openlayers-map';
|
|
2
2
|
import {
|
|
3
3
|
Controls,
|
|
4
4
|
Interactions,
|
|
5
5
|
Layer,
|
|
6
|
-
Map,
|
|
7
6
|
Layers,
|
|
7
|
+
Map,
|
|
8
8
|
} from '@eeacms/volto-openlayers-map/api';
|
|
9
|
-
import {
|
|
9
|
+
import React, { useState } from 'react';
|
|
10
|
+
import { useMapContext } from '@eeacms/volto-openlayers-map/hocs';
|
|
11
|
+
|
|
12
|
+
function PinInteraction({ longitude, latitude, onChange }) {
|
|
13
|
+
const mapContext = useMapContext();
|
|
14
|
+
const { addLayer, addInteraction, map } = mapContext;
|
|
15
|
+
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
if (
|
|
18
|
+
!map ||
|
|
19
|
+
typeof latitude === 'undefined' ||
|
|
20
|
+
typeof longitude === 'undefined'
|
|
21
|
+
)
|
|
22
|
+
return;
|
|
23
|
+
|
|
24
|
+
// Create a feature (the pin) and set it to be draggable
|
|
25
|
+
const pin = new ol.ol.Feature({
|
|
26
|
+
geometry: new ol.geom.Point(ol.proj.fromLonLat([longitude, latitude])), // Initial location
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Style for the pin
|
|
30
|
+
pin.setStyle(
|
|
31
|
+
new ol.style.Style({
|
|
32
|
+
image: new ol.style.Icon({
|
|
33
|
+
anchor: [0.5, 1],
|
|
34
|
+
src: 'https://openlayers.org/en/latest/examples/data/icon.png',
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Create a vector layer to hold the pin
|
|
40
|
+
const vectorSource = new ol.source.Vector({
|
|
41
|
+
features: [pin],
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const vectorLayer = new ol.layer.Vector({
|
|
45
|
+
source: vectorSource,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
map.addLayer(vectorLayer);
|
|
49
|
+
|
|
50
|
+
// Add drag interaction
|
|
51
|
+
const dragInteraction = new ol.interaction.Modify({
|
|
52
|
+
source: vectorSource,
|
|
53
|
+
pixelTolerance: 20,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
map.addInteraction(dragInteraction);
|
|
57
|
+
|
|
58
|
+
// Log the new position when the pin is dragged
|
|
59
|
+
dragInteraction.on('modifyend', function (event) {
|
|
60
|
+
const feature = event.features.getArray()[0];
|
|
61
|
+
const coordinates = feature.getGeometry().getCoordinates();
|
|
62
|
+
const lonLat = ol.proj.toLonLat(coordinates);
|
|
63
|
+
const [longitude, latitude] = lonLat;
|
|
64
|
+
onChange({ latitude, longitude });
|
|
65
|
+
});
|
|
66
|
+
}, [addInteraction, addLayer, map, onChange, latitude, longitude]);
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
10
70
|
|
|
11
71
|
const TileSetLoader = (props) => {
|
|
12
72
|
const [tileWMSSources, setTileWMSSources] = useState([]);
|
|
@@ -31,7 +91,7 @@ const TileSetLoader = (props) => {
|
|
|
31
91
|
};
|
|
32
92
|
|
|
33
93
|
const MapContainer = (props) => {
|
|
34
|
-
const { longitude, latitude, source } = props;
|
|
94
|
+
const { longitude, latitude, source, onChange } = props;
|
|
35
95
|
return (
|
|
36
96
|
<Map
|
|
37
97
|
view={{
|
|
@@ -44,15 +104,20 @@ const MapContainer = (props) => {
|
|
|
44
104
|
>
|
|
45
105
|
<Layers>
|
|
46
106
|
<Controls attribution={false} zoom={false} />
|
|
107
|
+
<PinInteraction
|
|
108
|
+
latitude={latitude}
|
|
109
|
+
longitude={longitude}
|
|
110
|
+
onChange={onChange}
|
|
111
|
+
/>
|
|
47
112
|
<Interactions
|
|
48
113
|
doubleClickZoom={true}
|
|
49
|
-
dragAndDrop={
|
|
114
|
+
dragAndDrop={true}
|
|
50
115
|
dragPan={true}
|
|
51
116
|
keyboardPan={true}
|
|
52
117
|
keyboardZoom={true}
|
|
53
118
|
mouseWheelZoom={true}
|
|
54
119
|
pointer={true}
|
|
55
|
-
select={
|
|
120
|
+
select={true}
|
|
56
121
|
/>
|
|
57
122
|
<Layer.Tile source={source} zIndex={0} />
|
|
58
123
|
</Layers>
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import loadable from '@loadable/component';
|
|
1
2
|
import { compose } from 'redux';
|
|
2
3
|
import { Sitemap } from '@plone/volto/components';
|
|
3
4
|
import DefaultView from '@plone/volto/components/theme/View/DefaultView';
|
|
@@ -35,6 +36,7 @@ import europeanComissionLogo from '@eeacms/volto-cca-policy/../theme/assets/imag
|
|
|
35
36
|
import eeaWhiteLogo from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/logo/eea-logo-white.svg';
|
|
36
37
|
|
|
37
38
|
import './slate-styles.less';
|
|
39
|
+
import BrokenLinks from './components/theme/Views/BrokenLinks';
|
|
38
40
|
|
|
39
41
|
const getEnv = () => (typeof window !== 'undefined' ? window.env : process.env);
|
|
40
42
|
|
|
@@ -59,8 +61,8 @@ const applyConfig = (config) => {
|
|
|
59
61
|
...(config.settings.externalRoutes || []),
|
|
60
62
|
{
|
|
61
63
|
match: {
|
|
62
|
-
path: new RegExp(voltoLocationsRegex),
|
|
63
64
|
exact: false,
|
|
65
|
+
path: new RegExp(voltoLocationsRegex),
|
|
64
66
|
strict: false,
|
|
65
67
|
},
|
|
66
68
|
url(payload) {
|
|
@@ -75,10 +77,10 @@ const applyConfig = (config) => {
|
|
|
75
77
|
'nominatim.openstreetmap.org',
|
|
76
78
|
];
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
if (!config.settings.loadables.reactTable)
|
|
81
|
+
config.settings.loadables.reactTable = loadable.lib(() =>
|
|
82
|
+
import('@tanstack/react-table'),
|
|
83
|
+
);
|
|
82
84
|
|
|
83
85
|
config.settings.dateLocale = 'en-gb';
|
|
84
86
|
config.settings.isMultilingual = true;
|
|
@@ -376,9 +378,19 @@ const applyConfig = (config) => {
|
|
|
376
378
|
component: Sitemap,
|
|
377
379
|
},
|
|
378
380
|
|
|
381
|
+
{
|
|
382
|
+
path: `/broken-links`,
|
|
383
|
+
component: BrokenLinks,
|
|
384
|
+
},
|
|
385
|
+
|
|
379
386
|
...(config.addonRoutes || []),
|
|
380
387
|
];
|
|
381
388
|
|
|
389
|
+
config.settings.nonContentRoutes = [
|
|
390
|
+
...config.settings.nonContentRoutes,
|
|
391
|
+
'/broken-links',
|
|
392
|
+
];
|
|
393
|
+
|
|
382
394
|
config.settings.appExtras = [
|
|
383
395
|
...(config.settings.appExtras || []),
|
|
384
396
|
{
|