@defra/forms-engine-plugin 4.3.0 → 4.5.0
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/.public/javascripts/application.min.js +1 -1
- package/.public/javascripts/application.min.js.map +1 -1
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.public/javascripts/vendor/accessible-autocomplete.min.js.map +1 -1
- package/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/javascripts/file-upload.js +13 -8
- package/.server/client/javascripts/file-upload.js.map +1 -1
- package/.server/client/javascripts/geospatial-map.d.ts +189 -0
- package/.server/client/javascripts/geospatial-map.js +1068 -0
- package/.server/client/javascripts/geospatial-map.js.map +1 -0
- package/.server/client/javascripts/location-map.d.ts +6 -91
- package/.server/client/javascripts/location-map.js +78 -385
- package/.server/client/javascripts/location-map.js.map +1 -1
- package/.server/client/javascripts/map.d.ts +199 -0
- package/.server/client/javascripts/map.js +384 -0
- package/.server/client/javascripts/map.js.map +1 -0
- package/.server/client/javascripts/shared.d.ts +3 -1
- package/.server/client/javascripts/shared.js +3 -1
- package/.server/client/javascripts/shared.js.map +1 -1
- package/.server/client/stylesheets/shared.scss +7 -0
- package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -0
- package/.server/server/plugins/engine/components/ComponentBase.js +2 -0
- package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.d.ts +3 -2
- package/.server/server/plugins/engine/components/FileUploadField.js +11 -3
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/components/FormComponent.d.ts +9 -1
- package/.server/server/plugins/engine/components/FormComponent.js +22 -0
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/GeospatialField.d.ts +77 -0
- package/.server/server/plugins/engine/components/GeospatialField.js +102 -0
- package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -0
- package/.server/server/plugins/engine/components/helpers/__stubs__/geospatial.d.ts +3 -0
- package/.server/server/plugins/engine/components/helpers/__stubs__/geospatial.js +63 -0
- package/.server/server/plugins/engine/components/helpers/__stubs__/geospatial.js.map +1 -0
- package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
- package/.server/server/plugins/engine/components/helpers/components.js +7 -0
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +6 -0
- package/.server/server/plugins/engine/components/helpers/geospatial.js +71 -0
- package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -0
- package/.server/server/plugins/engine/components/helpers/geospatial.test.js +42 -0
- package/.server/server/plugins/engine/components/helpers/geospatial.test.js.map +1 -0
- package/.server/server/plugins/engine/components/index.d.ts +1 -0
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +11 -0
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +65 -28
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/PageController.d.ts +1 -0
- package/.server/server/plugins/engine/pageControllers/PageController.js +2 -0
- package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js +13 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js +2 -1
- package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +63 -2
- package/.server/server/plugins/engine/types.js +33 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/views/components/geospatialfield.html +7 -0
- package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/routes/types.js.map +1 -1
- package/.server/server/services/cacheService.js +3 -0
- package/.server/server/services/cacheService.js.map +1 -1
- package/package.json +9 -5
- package/src/client/javascripts/file-upload.js +12 -8
- package/src/client/javascripts/geospatial-map.js +1023 -0
- package/src/client/javascripts/location-map.js +94 -390
- package/src/client/javascripts/map.js +389 -0
- package/src/client/javascripts/shared.js +3 -1
- package/src/client/stylesheets/shared.scss +7 -0
- package/src/server/plugins/engine/components/ComponentBase.ts +2 -0
- package/src/server/plugins/engine/components/FileUploadField.test.ts +11 -8
- package/src/server/plugins/engine/components/FileUploadField.ts +14 -5
- package/src/server/plugins/engine/components/FormComponent.ts +29 -0
- package/src/server/plugins/engine/components/GeospatialField.test.ts +380 -0
- package/src/server/plugins/engine/components/GeospatialField.ts +145 -0
- package/src/server/plugins/engine/components/helpers/__stubs__/geospatial.ts +85 -0
- package/src/server/plugins/engine/components/helpers/components.test.ts +44 -0
- package/src/server/plugins/engine/components/helpers/components.ts +10 -0
- package/src/server/plugins/engine/components/helpers/geospatial.test.js +55 -0
- package/src/server/plugins/engine/components/helpers/geospatial.ts +93 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +109 -5
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +69 -21
- package/src/server/plugins/engine/pageControllers/PageController.ts +2 -0
- package/src/server/plugins/engine/pageControllers/helpers/submission.test.ts +74 -0
- package/src/server/plugins/engine/pageControllers/helpers/submission.ts +17 -1
- package/src/server/plugins/engine/pageControllers/validationOptions.ts +3 -1
- package/src/server/plugins/engine/types.ts +77 -4
- package/src/server/plugins/engine/views/components/geospatialfield.html +7 -0
- package/src/server/plugins/nunjucks/context.test.js +2 -3
- package/src/server/routes/types.ts +4 -2
- package/src/server/services/cacheService.ts +2 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { centroid } from '@turf/centroid'
|
|
2
|
+
// @ts-expect-error - no types
|
|
3
|
+
import OsGridRef, { LatLon } from 'geodesy/osgridref.js'
|
|
4
|
+
|
|
5
|
+
import { processGeospatial } from '~/src/client/javascripts/geospatial-map.js'
|
|
6
|
+
import { processLocation } from '~/src/client/javascripts/location-map.js'
|
|
7
|
+
|
|
8
|
+
// Center of UK
|
|
9
|
+
const DEFAULT_LAT = 53.825564
|
|
10
|
+
const DEFAULT_LONG = -2.421975
|
|
11
|
+
const COMPANY_SYMBOL_CODE = 169
|
|
12
|
+
|
|
13
|
+
const defaultData = {
|
|
14
|
+
VTS_OUTDOOR_URL: '/api/maps/vts/OS_VTS_3857_Outdoor.json',
|
|
15
|
+
VTS_DARK_URL: '/api/maps/vts/OS_VTS_3857_Dark.json',
|
|
16
|
+
VTS_BLACK_AND_WHITE_URL: '/api/maps/vts/OS_VTS_3857_Black_and_White.json'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Converts lat long to easting and northing
|
|
21
|
+
* @param {object} param
|
|
22
|
+
* @param {number} param.lat
|
|
23
|
+
* @param {number} param.long
|
|
24
|
+
* @returns {{ easting: number, northing: number }}
|
|
25
|
+
*/
|
|
26
|
+
export function latLongToEastingNorthing({ lat, long }) {
|
|
27
|
+
const point = new LatLon(lat, long)
|
|
28
|
+
|
|
29
|
+
return point.toOsGrid()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Converts easting and northing to lat long
|
|
34
|
+
* @param {object} param
|
|
35
|
+
* @param {number} param.easting
|
|
36
|
+
* @param {number} param.northing
|
|
37
|
+
* @returns {{ lat: number, long: number }}
|
|
38
|
+
*/
|
|
39
|
+
export function eastingNorthingToLatLong({ easting, northing }) {
|
|
40
|
+
const point = new OsGridRef(easting, northing)
|
|
41
|
+
const latLong = point.toLatLon()
|
|
42
|
+
|
|
43
|
+
return { lat: latLong.latitude, long: latLong.longitude }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Converts lat long to an ordnance survey grid reference
|
|
48
|
+
* @param {object} param
|
|
49
|
+
* @param {number} param.lat
|
|
50
|
+
* @param {number} param.long
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function latLongToOsGridRef({ lat, long }) {
|
|
54
|
+
const point = new LatLon(lat, long)
|
|
55
|
+
|
|
56
|
+
return point.toOsGrid().toString()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Converts an ordnance survey grid reference to lat long
|
|
61
|
+
* @param {string} osGridRef
|
|
62
|
+
* @returns {{ lat: number, long: number }}
|
|
63
|
+
*/
|
|
64
|
+
export function osGridRefToLatLong(osGridRef) {
|
|
65
|
+
const point = OsGridRef.parse(osGridRef)
|
|
66
|
+
const latLong = point.toLatLon()
|
|
67
|
+
|
|
68
|
+
return { lat: latLong.latitude, long: latLong.longitude }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the grid ref from the first coordinate of a long/lat feature
|
|
73
|
+
* @param {Feature} feature
|
|
74
|
+
*/
|
|
75
|
+
export function getCoordinateGridRef(feature) {
|
|
76
|
+
if (feature.geometry.type === 'Point') {
|
|
77
|
+
const [long, lat] = feature.geometry.coordinates
|
|
78
|
+
const point = new LatLon(lat, long)
|
|
79
|
+
|
|
80
|
+
return point.toOsGrid().toString()
|
|
81
|
+
} else if (feature.geometry.type === 'LineString') {
|
|
82
|
+
const [long, lat] = feature.geometry.coordinates[0]
|
|
83
|
+
const point = new LatLon(lat, long)
|
|
84
|
+
|
|
85
|
+
return point.toOsGrid().toString()
|
|
86
|
+
} else {
|
|
87
|
+
const [long, lat] = feature.geometry.coordinates[0][0]
|
|
88
|
+
const point = new LatLon(lat, long)
|
|
89
|
+
|
|
90
|
+
return point.toOsGrid().toString()
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get the centroid grid ref from a long/lat feature
|
|
96
|
+
* @param {Feature} feature
|
|
97
|
+
*/
|
|
98
|
+
export function getCentroidGridRef(feature) {
|
|
99
|
+
if (feature.geometry.type === 'Point') {
|
|
100
|
+
const [long, lat] = feature.geometry.coordinates
|
|
101
|
+
const point = new LatLon(lat, long)
|
|
102
|
+
|
|
103
|
+
return point.toOsGrid().toString()
|
|
104
|
+
} else {
|
|
105
|
+
const centre = centroid(feature)
|
|
106
|
+
const [long, lat] = centre.geometry.coordinates
|
|
107
|
+
const point = new LatLon(lat, long)
|
|
108
|
+
|
|
109
|
+
return point.toOsGrid().toString()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @type {InteractiveMapInitConfig} */
|
|
114
|
+
export const defaultConfig = {
|
|
115
|
+
zoom: '6',
|
|
116
|
+
center: [DEFAULT_LONG, DEFAULT_LAT]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const EVENTS = {
|
|
120
|
+
mapReady: 'map:ready',
|
|
121
|
+
interactMarkerChange: 'interact:markerchange',
|
|
122
|
+
drawReady: 'draw:ready',
|
|
123
|
+
drawCreated: 'draw:created',
|
|
124
|
+
drawEdited: 'draw:edited',
|
|
125
|
+
drawCancelled: 'draw:cancelled'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Make a form submit handler that only allows submissions from allowed buttons
|
|
130
|
+
* @param {HTMLButtonElement[]} buttons - the form buttons to allow submissions
|
|
131
|
+
*/
|
|
132
|
+
export function formSubmitFactory(buttons) {
|
|
133
|
+
/**
|
|
134
|
+
* The submit handler
|
|
135
|
+
* @param {SubmitEvent} e
|
|
136
|
+
*/
|
|
137
|
+
const onFormSubmit = function (e) {
|
|
138
|
+
if (
|
|
139
|
+
!(e.submitter instanceof HTMLButtonElement) ||
|
|
140
|
+
!buttons.includes(e.submitter)
|
|
141
|
+
) {
|
|
142
|
+
e.preventDefault()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return onFormSubmit
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Initialise location maps
|
|
151
|
+
* @param {Partial<MapsEnvironmentConfig>} config - the map configuration
|
|
152
|
+
*/
|
|
153
|
+
export function initMaps(config = {}) {
|
|
154
|
+
const {
|
|
155
|
+
assetPath = '/assets',
|
|
156
|
+
apiPath = '/form/api',
|
|
157
|
+
data = defaultData
|
|
158
|
+
} = config
|
|
159
|
+
const locations = document.querySelectorAll('.app-location-field')
|
|
160
|
+
const geospatials = document.querySelectorAll('.app-geospatial-field')
|
|
161
|
+
|
|
162
|
+
// TODO: Fix this in `interactive-map`
|
|
163
|
+
// If there are location components on the page fix up the main form submit
|
|
164
|
+
// handler so it doesn't fire when using the integrated map search feature
|
|
165
|
+
if (locations.length) {
|
|
166
|
+
const form = locations[0].closest('form')
|
|
167
|
+
|
|
168
|
+
if (form === null) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const buttons = Array.from(form.querySelectorAll('button'))
|
|
173
|
+
form.addEventListener('submit', formSubmitFactory(buttons), false)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (geospatials.length) {
|
|
177
|
+
const form = geospatials[0].closest('form')
|
|
178
|
+
|
|
179
|
+
if (form === null) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const buttons = Array.from(form.querySelectorAll('button'))
|
|
184
|
+
form.addEventListener('submit', formSubmitFactory(buttons), false)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
locations.forEach((location, index) => {
|
|
188
|
+
processLocation({ assetPath, apiPath, data }, location, index)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
geospatials.forEach((geospatial, index) => {
|
|
192
|
+
processGeospatial({ assetPath, apiPath, data }, geospatial, index)
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* OS API request proxy factory
|
|
198
|
+
* @param {string} apiPath - the root API path
|
|
199
|
+
*/
|
|
200
|
+
export function makeTileRequestTransformer(apiPath) {
|
|
201
|
+
/**
|
|
202
|
+
* Proxy OS API requests via our server
|
|
203
|
+
* @param {string} url - the request URL
|
|
204
|
+
* @param {string} resourceType - the resource type
|
|
205
|
+
*/
|
|
206
|
+
return function transformTileRequest(url, resourceType) {
|
|
207
|
+
if (url.startsWith('https://api.os.uk')) {
|
|
208
|
+
if (resourceType === 'Tile') {
|
|
209
|
+
return {
|
|
210
|
+
url: url.replace(
|
|
211
|
+
'https://api.os.uk/maps/vector/v1/vts',
|
|
212
|
+
`${window.location.origin}${apiPath}`
|
|
213
|
+
),
|
|
214
|
+
headers: {}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (resourceType !== 'Style') {
|
|
219
|
+
return {
|
|
220
|
+
url: `${apiPath}/map-proxy?url=${encodeURIComponent(url)}`,
|
|
221
|
+
headers: {}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const spritesPath =
|
|
227
|
+
'https://raw.githubusercontent.com/OrdnanceSurvey/OS-Vector-Tile-API-Stylesheets/main'
|
|
228
|
+
|
|
229
|
+
// Proxy sprite requests
|
|
230
|
+
if (url.startsWith(spritesPath)) {
|
|
231
|
+
const path = url.substring(spritesPath.length)
|
|
232
|
+
return {
|
|
233
|
+
url: `${apiPath}/maps/vts${path}`,
|
|
234
|
+
headers: {}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { url, headers: {} }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Create a Defra map instance
|
|
244
|
+
* @param {string} mapId - the map id
|
|
245
|
+
* @param {InteractiveMapInitConfig} initConfig - the map initial configuration
|
|
246
|
+
* @param {MapsEnvironmentConfig} mapsConfig - the map environment params
|
|
247
|
+
*/
|
|
248
|
+
export function createMap(mapId, initConfig, mapsConfig) {
|
|
249
|
+
const { assetPath, apiPath, data = defaultData } = mapsConfig
|
|
250
|
+
const logoAltText = 'Ordnance survey logo'
|
|
251
|
+
|
|
252
|
+
// @ts-expect-error - Defra namespace currently comes from UMD support files
|
|
253
|
+
const defra = window.defra
|
|
254
|
+
|
|
255
|
+
const interactPlugin = defra.interactPlugin({
|
|
256
|
+
markerColor: { outdoor: '#ff0000', dark: '#00ff00' },
|
|
257
|
+
interactionMode: 'marker',
|
|
258
|
+
multiSelect: false
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
/** @type {InteractiveMap} */
|
|
262
|
+
const map = new defra.InteractiveMap(mapId, {
|
|
263
|
+
enableFullscreen: true,
|
|
264
|
+
autoColorScheme: false,
|
|
265
|
+
mapProvider: defra.maplibreProvider(),
|
|
266
|
+
reverseGeocodeProvider: defra.openNamesProvider({
|
|
267
|
+
url: `${apiPath}/reverse-geocode-proxy?easting={easting}&northing={northing}`
|
|
268
|
+
}),
|
|
269
|
+
behaviour: 'inline',
|
|
270
|
+
minZoom: 6,
|
|
271
|
+
maxZoom: 18,
|
|
272
|
+
containerHeight: '400px',
|
|
273
|
+
enableZoomControls: true,
|
|
274
|
+
transformRequest: makeTileRequestTransformer(apiPath),
|
|
275
|
+
...initConfig,
|
|
276
|
+
plugins: [
|
|
277
|
+
defra.mapStylesPlugin({
|
|
278
|
+
mapStyles: [
|
|
279
|
+
{
|
|
280
|
+
id: 'outdoor',
|
|
281
|
+
label: 'Outdoor',
|
|
282
|
+
url: data.VTS_OUTDOOR_URL,
|
|
283
|
+
thumbnail: `${assetPath}/interactive-map/assets/images/outdoor-map-thumb.jpg`,
|
|
284
|
+
logo: `${assetPath}/interactive-map/assets/images/os-logo.svg`,
|
|
285
|
+
logoAltText,
|
|
286
|
+
attribution: `Contains OS data ${String.fromCodePoint(COMPANY_SYMBOL_CODE)} Crown copyright and database rights ${new Date().getFullYear()}`,
|
|
287
|
+
backgroundColor: '#f5f5f0'
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: 'dark',
|
|
291
|
+
label: 'Dark',
|
|
292
|
+
url: data.VTS_DARK_URL,
|
|
293
|
+
mapColorScheme: 'dark',
|
|
294
|
+
appColorScheme: 'dark',
|
|
295
|
+
thumbnail: `${assetPath}/interactive-map/assets/images/dark-map-thumb.jpg`,
|
|
296
|
+
logo: `${assetPath}/interactive-map/assets/images/os-logo-white.svg`,
|
|
297
|
+
logoAltText,
|
|
298
|
+
attribution: `Contains OS data ${String.fromCodePoint(COMPANY_SYMBOL_CODE)} Crown copyright and database rights ${new Date().getFullYear()}`
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'black-and-white',
|
|
302
|
+
label: 'Black/White',
|
|
303
|
+
url: data.VTS_BLACK_AND_WHITE_URL,
|
|
304
|
+
thumbnail: `${assetPath}/interactive-map/assets/images/black-and-white-map-thumb.jpg`,
|
|
305
|
+
logo: `${assetPath}/interactive-map/assets/images/os-logo-black.svg`,
|
|
306
|
+
logoAltText,
|
|
307
|
+
attribution: `Contains OS data ${String.fromCodePoint(COMPANY_SYMBOL_CODE)} Crown copyright and database rights ${new Date().getFullYear()}`
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}),
|
|
311
|
+
interactPlugin,
|
|
312
|
+
defra.searchPlugin({
|
|
313
|
+
osNamesURL: `${apiPath}/geocode-proxy?query={query}`,
|
|
314
|
+
width: '300px',
|
|
315
|
+
showMarker: false
|
|
316
|
+
}),
|
|
317
|
+
defra.scaleBarPlugin({
|
|
318
|
+
units: 'metric'
|
|
319
|
+
}),
|
|
320
|
+
...(initConfig.plugins ?? [])
|
|
321
|
+
]
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
return { map, interactPlugin }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Updates the marker position and moves the map view port the new location
|
|
329
|
+
* @param {InteractiveMap} map - the map component instance (of InteractiveMap)
|
|
330
|
+
* @param {MapLibreMap} mapProvider - the map provider instance (of MapLibreMap)
|
|
331
|
+
* @param {MapCenter} center - the point
|
|
332
|
+
*/
|
|
333
|
+
export function centerMap(map, mapProvider, center) {
|
|
334
|
+
// Move the 'location' marker to the new point
|
|
335
|
+
map.addMarker('location', center)
|
|
336
|
+
|
|
337
|
+
// Pan & zoom the map to the new valid location
|
|
338
|
+
mapProvider.flyTo({
|
|
339
|
+
center,
|
|
340
|
+
zoom: 14,
|
|
341
|
+
essential: true
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @typedef {object} InteractiveMap - an instance of a InteractiveMap
|
|
347
|
+
* @property {Function} on - register callback listeners to map events
|
|
348
|
+
* @property {Function} addPanel - adds a new panel to the map
|
|
349
|
+
* @property {Function} addMarker - adds/updates a marker
|
|
350
|
+
* @property {Function} removeMarker - removes a marker
|
|
351
|
+
* @property {Function} addButton - adds/updates a button
|
|
352
|
+
* @property {Function} toggleButtonState - toggle the state of a button
|
|
353
|
+
*/
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* @typedef {object} MapLibreMap
|
|
357
|
+
* @property {Function} flyTo - pans/zooms to a new location
|
|
358
|
+
* @property {Function} fitBounds - fits the my to the new bounds
|
|
359
|
+
*/
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @typedef {[number, number]} MapCenter - Map center point as [long, lat]
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @typedef {object} InteractiveMapInitConfig - additional config that can be provided to InteractiveMap
|
|
367
|
+
* @property {string} zoom - the zoom level of the map
|
|
368
|
+
* @property {MapCenter} center - the center point of the map
|
|
369
|
+
* @property {{ id: string, coords: MapCenter }[]} [markers] - the markers to add to the map
|
|
370
|
+
* @property {any[]} [plugins] - additional plugins
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @typedef {object} TileData
|
|
375
|
+
* @property {string} VTS_OUTDOOR_URL - the outdoor tile URL
|
|
376
|
+
* @property {string} VTS_DARK_URL - the dark tile URL
|
|
377
|
+
* @property {string} VTS_BLACK_AND_WHITE_URL - the black and white tile URL
|
|
378
|
+
*/
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @typedef {object} MapsEnvironmentConfig
|
|
382
|
+
* @property {string} assetPath - the root asset path
|
|
383
|
+
* @property {string} apiPath - the root API path
|
|
384
|
+
* @property {TileData} data - the tile data config
|
|
385
|
+
*/
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @import { Feature } from '~/src/server/plugins/engine/types.js'
|
|
389
|
+
*/
|
|
@@ -2,7 +2,9 @@ import { initAllAutocomplete as initAllAutocompleteImp } from '~/src/client/java
|
|
|
2
2
|
import { initFileUpload as initFileUploadImp } from '~/src/client/javascripts/file-upload.js'
|
|
3
3
|
import { initAllGovuk as initAllGovukImp } from '~/src/client/javascripts/govuk.js'
|
|
4
4
|
import { initPreviewCloseLink as initPreviewCloseLinkImp } from '~/src/client/javascripts/preview-close-link.js'
|
|
5
|
-
export { initMaps } from '~/src/client/javascripts/
|
|
5
|
+
export { initMaps } from '~/src/client/javascripts/map.js'
|
|
6
|
+
export * as map from '~/src/client/javascripts/map.js'
|
|
7
|
+
export * as geospatialMap from '~/src/client/javascripts/geospatial-map.js'
|
|
6
8
|
|
|
7
9
|
export const initAllGovuk = initAllGovukImp
|
|
8
10
|
export const initAllAutocomplete = initAllAutocompleteImp
|
|
@@ -15,6 +15,7 @@ import { type FormModel } from '~/src/server/plugins/engine/models/index.js'
|
|
|
15
15
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
16
16
|
|
|
17
17
|
export class ComponentBase {
|
|
18
|
+
id?: string
|
|
18
19
|
page?: PageControllerClass
|
|
19
20
|
parent: Component | undefined
|
|
20
21
|
collection: ComponentCollection | undefined
|
|
@@ -40,6 +41,7 @@ export class ComponentBase {
|
|
|
40
41
|
model: FormModel
|
|
41
42
|
}
|
|
42
43
|
) {
|
|
44
|
+
this.id = def.id
|
|
43
45
|
this.type = def.type
|
|
44
46
|
this.name = def.name
|
|
45
47
|
this.title = def.title
|
|
@@ -405,7 +405,7 @@ describe('FileUploadField', () => {
|
|
|
405
405
|
actions: {
|
|
406
406
|
items: [
|
|
407
407
|
{
|
|
408
|
-
href: `/test/file-upload-component/${validState[0].
|
|
408
|
+
href: `/test/file-upload-component/${validState[0].status.form.file.fileId}/confirm-delete`,
|
|
409
409
|
text: 'Remove',
|
|
410
410
|
attributes: { id: 'myComponent__0' },
|
|
411
411
|
classes: 'govuk-link--no-visited-state',
|
|
@@ -424,7 +424,7 @@ describe('FileUploadField', () => {
|
|
|
424
424
|
actions: {
|
|
425
425
|
items: [
|
|
426
426
|
{
|
|
427
|
-
href: `/test/file-upload-component/${validState[1].
|
|
427
|
+
href: `/test/file-upload-component/${validState[1].status.form.file.fileId}/confirm-delete`,
|
|
428
428
|
text: 'Remove',
|
|
429
429
|
attributes: { id: 'myComponent__1' },
|
|
430
430
|
classes: 'govuk-link--no-visited-state',
|
|
@@ -443,7 +443,7 @@ describe('FileUploadField', () => {
|
|
|
443
443
|
actions: {
|
|
444
444
|
items: [
|
|
445
445
|
{
|
|
446
|
-
href: `/test/file-upload-component/${validState[2].
|
|
446
|
+
href: `/test/file-upload-component/${validState[2].status.form.file.fileId}/confirm-delete`,
|
|
447
447
|
text: 'Remove',
|
|
448
448
|
attributes: { id: 'myComponent__2' },
|
|
449
449
|
classes: 'govuk-link--no-visited-state',
|
|
@@ -454,7 +454,8 @@ describe('FileUploadField', () => {
|
|
|
454
454
|
}
|
|
455
455
|
]
|
|
456
456
|
}
|
|
457
|
-
}
|
|
457
|
+
},
|
|
458
|
+
multiple: true
|
|
458
459
|
})
|
|
459
460
|
)
|
|
460
461
|
})
|
|
@@ -543,7 +544,7 @@ describe('FileUploadField', () => {
|
|
|
543
544
|
actions: {
|
|
544
545
|
items: [
|
|
545
546
|
{
|
|
546
|
-
href: `/test/file-upload-component/${validState[2].
|
|
547
|
+
href: `/test/file-upload-component/${validState[2].status.form.file.fileId}/confirm-delete`,
|
|
547
548
|
text: 'Remove',
|
|
548
549
|
attributes: { id: 'myComponent__0' },
|
|
549
550
|
classes: 'govuk-link--no-visited-state',
|
|
@@ -554,7 +555,8 @@ describe('FileUploadField', () => {
|
|
|
554
555
|
}
|
|
555
556
|
]
|
|
556
557
|
}
|
|
557
|
-
}
|
|
558
|
+
},
|
|
559
|
+
multiple: true
|
|
558
560
|
})
|
|
559
561
|
)
|
|
560
562
|
})
|
|
@@ -583,7 +585,7 @@ describe('FileUploadField', () => {
|
|
|
583
585
|
actions: {
|
|
584
586
|
items: [
|
|
585
587
|
{
|
|
586
|
-
href: `/test/file-upload-component/${validState[2].
|
|
588
|
+
href: `/test/file-upload-component/${validState[2].status.form.file.fileId}/confirm-delete`,
|
|
587
589
|
text: 'Remove',
|
|
588
590
|
attributes: { id: 'myComponent__0' },
|
|
589
591
|
classes: 'govuk-link--no-visited-state',
|
|
@@ -594,7 +596,8 @@ describe('FileUploadField', () => {
|
|
|
594
596
|
}
|
|
595
597
|
]
|
|
596
598
|
}
|
|
597
|
-
}
|
|
599
|
+
},
|
|
600
|
+
multiple: true
|
|
598
601
|
})
|
|
599
602
|
)
|
|
600
603
|
})
|
|
@@ -73,9 +73,12 @@ export const tempStatusSchema = joi
|
|
|
73
73
|
.valid(UploadStatus.ready, UploadStatus.pending)
|
|
74
74
|
.required(),
|
|
75
75
|
metadata: metadataSchema,
|
|
76
|
-
form: joi
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
form: joi
|
|
77
|
+
.object()
|
|
78
|
+
.required()
|
|
79
|
+
.keys({
|
|
80
|
+
file: joi.array().items(tempFileSchema).single().required()
|
|
81
|
+
}),
|
|
79
82
|
numberOfRejectedFiles: joi.number().optional()
|
|
80
83
|
})
|
|
81
84
|
.required()
|
|
@@ -191,7 +194,7 @@ export class FileUploadField extends FormComponent {
|
|
|
191
194
|
errors?: FormSubmissionError[],
|
|
192
195
|
query: FormQuery = {}
|
|
193
196
|
) {
|
|
194
|
-
const { options, page } = this
|
|
197
|
+
const { options, page, schema } = this
|
|
195
198
|
|
|
196
199
|
// Allow preview URL direct access
|
|
197
200
|
const isForceAccess = 'force' in query
|
|
@@ -233,7 +236,7 @@ export class FileUploadField extends FormComponent {
|
|
|
233
236
|
|
|
234
237
|
// Remove summary list actions from previews
|
|
235
238
|
if (!isForceAccess) {
|
|
236
|
-
const path = `/${
|
|
239
|
+
const path = `/${file.fileId}/confirm-delete`
|
|
237
240
|
const href = page?.getHref(`${page.path}${path}`) ?? '#'
|
|
238
241
|
|
|
239
242
|
items.push({
|
|
@@ -263,6 +266,9 @@ export class FileUploadField extends FormComponent {
|
|
|
263
266
|
attributes.accept = options.accept
|
|
264
267
|
}
|
|
265
268
|
|
|
269
|
+
// Allow multiple file selection when schema permits more than 1 file
|
|
270
|
+
const allowsMultiple = schema.max !== 1 && schema.length !== 1
|
|
271
|
+
|
|
266
272
|
const summaryList: SummaryList = {
|
|
267
273
|
classes: 'govuk-summary-list--long-key',
|
|
268
274
|
rows
|
|
@@ -277,6 +283,9 @@ export class FileUploadField extends FormComponent {
|
|
|
277
283
|
// Override the component name we send to CDP
|
|
278
284
|
name: 'file',
|
|
279
285
|
|
|
286
|
+
// Enable multi-file selection in the file picker
|
|
287
|
+
...(allowsMultiple && { multiple: true }),
|
|
288
|
+
|
|
280
289
|
upload: {
|
|
281
290
|
count,
|
|
282
291
|
summaryList
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '~/src/server/plugins/engine/types/index.js'
|
|
13
13
|
import {
|
|
14
14
|
type ErrorMessageTemplateList,
|
|
15
|
+
type Feature,
|
|
15
16
|
type FileState,
|
|
16
17
|
type FormPayload,
|
|
17
18
|
type FormState,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
type FormSubmissionError,
|
|
20
21
|
type FormSubmissionState,
|
|
21
22
|
type FormValue,
|
|
23
|
+
type GeospatialState,
|
|
22
24
|
type RepeatItemState,
|
|
23
25
|
type RepeatListState,
|
|
24
26
|
type UploadState
|
|
@@ -302,9 +304,36 @@ export function isUploadState(value?: unknown): value is UploadState {
|
|
|
302
304
|
return value.every(isUploadValue)
|
|
303
305
|
}
|
|
304
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Check for geospatial state
|
|
309
|
+
*/
|
|
310
|
+
export function isGeospatialState(value?: unknown): value is GeospatialState {
|
|
311
|
+
if (!Array.isArray(value)) {
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Skip checks when empty
|
|
316
|
+
if (!value.length) {
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return value.every(isGeospatialValue)
|
|
321
|
+
}
|
|
322
|
+
|
|
305
323
|
/**
|
|
306
324
|
* Check for upload state value
|
|
307
325
|
*/
|
|
308
326
|
export function isUploadValue(value?: unknown): value is FileState {
|
|
309
327
|
return isFormState(value) && typeof value.uploadId === 'string'
|
|
310
328
|
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Check for geospatial state value
|
|
332
|
+
*/
|
|
333
|
+
export function isGeospatialValue(value?: unknown): value is Feature {
|
|
334
|
+
return (
|
|
335
|
+
isFormState(value) &&
|
|
336
|
+
typeof value.type === 'string' &&
|
|
337
|
+
value.type === 'Feature'
|
|
338
|
+
)
|
|
339
|
+
}
|