@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.
Files changed (97) hide show
  1. package/.public/javascripts/application.min.js +1 -1
  2. package/.public/javascripts/application.min.js.map +1 -1
  3. package/.public/javascripts/shared.min.js +1 -1
  4. package/.public/javascripts/shared.min.js.map +1 -1
  5. package/.public/javascripts/vendor/accessible-autocomplete.min.js.map +1 -1
  6. package/.public/stylesheets/application.min.css +1 -1
  7. package/.public/stylesheets/application.min.css.map +1 -1
  8. package/.server/client/javascripts/file-upload.js +13 -8
  9. package/.server/client/javascripts/file-upload.js.map +1 -1
  10. package/.server/client/javascripts/geospatial-map.d.ts +189 -0
  11. package/.server/client/javascripts/geospatial-map.js +1068 -0
  12. package/.server/client/javascripts/geospatial-map.js.map +1 -0
  13. package/.server/client/javascripts/location-map.d.ts +6 -91
  14. package/.server/client/javascripts/location-map.js +78 -385
  15. package/.server/client/javascripts/location-map.js.map +1 -1
  16. package/.server/client/javascripts/map.d.ts +199 -0
  17. package/.server/client/javascripts/map.js +384 -0
  18. package/.server/client/javascripts/map.js.map +1 -0
  19. package/.server/client/javascripts/shared.d.ts +3 -1
  20. package/.server/client/javascripts/shared.js +3 -1
  21. package/.server/client/javascripts/shared.js.map +1 -1
  22. package/.server/client/stylesheets/shared.scss +7 -0
  23. package/.server/server/plugins/engine/components/ComponentBase.d.ts +1 -0
  24. package/.server/server/plugins/engine/components/ComponentBase.js +2 -0
  25. package/.server/server/plugins/engine/components/ComponentBase.js.map +1 -1
  26. package/.server/server/plugins/engine/components/FileUploadField.d.ts +3 -2
  27. package/.server/server/plugins/engine/components/FileUploadField.js +11 -3
  28. package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
  29. package/.server/server/plugins/engine/components/FormComponent.d.ts +9 -1
  30. package/.server/server/plugins/engine/components/FormComponent.js +22 -0
  31. package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
  32. package/.server/server/plugins/engine/components/GeospatialField.d.ts +77 -0
  33. package/.server/server/plugins/engine/components/GeospatialField.js +102 -0
  34. package/.server/server/plugins/engine/components/GeospatialField.js.map +1 -0
  35. package/.server/server/plugins/engine/components/helpers/__stubs__/geospatial.d.ts +3 -0
  36. package/.server/server/plugins/engine/components/helpers/__stubs__/geospatial.js +63 -0
  37. package/.server/server/plugins/engine/components/helpers/__stubs__/geospatial.js.map +1 -0
  38. package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
  39. package/.server/server/plugins/engine/components/helpers/components.js +7 -0
  40. package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
  41. package/.server/server/plugins/engine/components/helpers/geospatial.d.ts +6 -0
  42. package/.server/server/plugins/engine/components/helpers/geospatial.js +71 -0
  43. package/.server/server/plugins/engine/components/helpers/geospatial.js.map +1 -0
  44. package/.server/server/plugins/engine/components/helpers/geospatial.test.js +42 -0
  45. package/.server/server/plugins/engine/components/helpers/geospatial.test.js.map +1 -0
  46. package/.server/server/plugins/engine/components/index.d.ts +1 -0
  47. package/.server/server/plugins/engine/components/index.js +1 -0
  48. package/.server/server/plugins/engine/components/index.js.map +1 -1
  49. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.d.ts +11 -0
  50. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +65 -28
  51. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  52. package/.server/server/plugins/engine/pageControllers/PageController.d.ts +1 -0
  53. package/.server/server/plugins/engine/pageControllers/PageController.js +2 -0
  54. package/.server/server/plugins/engine/pageControllers/PageController.js.map +1 -1
  55. package/.server/server/plugins/engine/pageControllers/helpers/submission.js +13 -1
  56. package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -1
  57. package/.server/server/plugins/engine/pageControllers/validationOptions.js +2 -1
  58. package/.server/server/plugins/engine/pageControllers/validationOptions.js.map +1 -1
  59. package/.server/server/plugins/engine/types.d.ts +63 -2
  60. package/.server/server/plugins/engine/types.js +33 -0
  61. package/.server/server/plugins/engine/types.js.map +1 -1
  62. package/.server/server/plugins/engine/views/components/geospatialfield.html +7 -0
  63. package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
  64. package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
  65. package/.server/server/routes/types.js.map +1 -1
  66. package/.server/server/services/cacheService.js +3 -0
  67. package/.server/server/services/cacheService.js.map +1 -1
  68. package/package.json +9 -5
  69. package/src/client/javascripts/file-upload.js +12 -8
  70. package/src/client/javascripts/geospatial-map.js +1023 -0
  71. package/src/client/javascripts/location-map.js +94 -390
  72. package/src/client/javascripts/map.js +389 -0
  73. package/src/client/javascripts/shared.js +3 -1
  74. package/src/client/stylesheets/shared.scss +7 -0
  75. package/src/server/plugins/engine/components/ComponentBase.ts +2 -0
  76. package/src/server/plugins/engine/components/FileUploadField.test.ts +11 -8
  77. package/src/server/plugins/engine/components/FileUploadField.ts +14 -5
  78. package/src/server/plugins/engine/components/FormComponent.ts +29 -0
  79. package/src/server/plugins/engine/components/GeospatialField.test.ts +380 -0
  80. package/src/server/plugins/engine/components/GeospatialField.ts +145 -0
  81. package/src/server/plugins/engine/components/helpers/__stubs__/geospatial.ts +85 -0
  82. package/src/server/plugins/engine/components/helpers/components.test.ts +44 -0
  83. package/src/server/plugins/engine/components/helpers/components.ts +10 -0
  84. package/src/server/plugins/engine/components/helpers/geospatial.test.js +55 -0
  85. package/src/server/plugins/engine/components/helpers/geospatial.ts +93 -0
  86. package/src/server/plugins/engine/components/index.ts +1 -0
  87. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +109 -5
  88. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +69 -21
  89. package/src/server/plugins/engine/pageControllers/PageController.ts +2 -0
  90. package/src/server/plugins/engine/pageControllers/helpers/submission.test.ts +74 -0
  91. package/src/server/plugins/engine/pageControllers/helpers/submission.ts +17 -1
  92. package/src/server/plugins/engine/pageControllers/validationOptions.ts +3 -1
  93. package/src/server/plugins/engine/types.ts +77 -4
  94. package/src/server/plugins/engine/views/components/geospatialfield.html +7 -0
  95. package/src/server/plugins/nunjucks/context.test.js +2 -3
  96. package/src/server/routes/types.ts +4 -2
  97. package/src/server/services/cacheService.ts +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geospatial.js","names":["validState","type","properties","description","coordinateGridReference","centroidGridReference","geometry","coordinates","id","validSingleState"],"sources":["../../../../../../../src/server/plugins/engine/components/helpers/__stubs__/geospatial.ts"],"sourcesContent":["import { type GeospatialState } from '~/src/server/plugins/engine/types.js'\n\nexport const validState: GeospatialState = [\n {\n type: 'Feature',\n properties: {\n description: 'My farm house',\n coordinateGridReference: 'ST 00001',\n centroidGridReference: 'ST 00001'\n },\n geometry: {\n coordinates: [-2.5723699109417737, 53.2380485215034],\n type: 'Point'\n },\n id: 'a'\n },\n {\n type: 'Feature',\n properties: {\n description: 'Main gas line',\n coordinateGridReference: 'ST 00001',\n centroidGridReference: 'ST 00001'\n },\n geometry: {\n coordinates: [\n [-2.570496516462896, 53.239162468888566],\n [-2.5722447488110447, 53.238174174285746]\n ],\n type: 'LineString'\n },\n id: 'b'\n },\n {\n type: 'Feature',\n properties: {\n description: 'My Pony Paddock',\n coordinateGridReference: 'ST 00001',\n centroidGridReference: 'ST 00001'\n },\n geometry: {\n coordinates: [\n [\n [-2.573552894955583, 53.238229751360706],\n [-2.5738557065633643, 53.23812342993719],\n [-2.5737507318720247, 53.23797119653088],\n [-2.573411582871387, 53.23785037598134],\n [-2.5727575097991178, 53.23787454011864],\n [-2.572858447002119, 53.23825391528342],\n [-2.573552894955583, 53.238229751360706]\n ]\n ],\n type: 'Polygon'\n },\n id: 'c'\n },\n {\n type: 'Feature',\n properties: {\n description: 'My farm house #2',\n coordinateGridReference: 'ST 00001',\n centroidGridReference: 'ST 00001'\n },\n geometry: {\n coordinates: [-2.5724, 53.239],\n type: 'Point'\n },\n id: 'd'\n }\n]\n\nexport const validSingleState: GeospatialState = [\n {\n type: 'Feature',\n properties: {\n description: 'My farm house',\n coordinateGridReference: 'ST 00001',\n centroidGridReference: 'ST 00001'\n },\n geometry: {\n coordinates: [-2.5723699109417737, 53.2380485215034],\n type: 'Point'\n },\n id: 'a'\n }\n]\n"],"mappings":"AAEA,OAAO,MAAMA,UAA2B,GAAG,CACzC;EACEC,IAAI,EAAE,SAAS;EACfC,UAAU,EAAE;IACVC,WAAW,EAAE,eAAe;IAC5BC,uBAAuB,EAAE,UAAU;IACnCC,qBAAqB,EAAE;EACzB,CAAC;EACDC,QAAQ,EAAE;IACRC,WAAW,EAAE,CAAC,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IACpDN,IAAI,EAAE;EACR,CAAC;EACDO,EAAE,EAAE;AACN,CAAC,EACD;EACEP,IAAI,EAAE,SAAS;EACfC,UAAU,EAAE;IACVC,WAAW,EAAE,eAAe;IAC5BC,uBAAuB,EAAE,UAAU;IACnCC,qBAAqB,EAAE;EACzB,CAAC;EACDC,QAAQ,EAAE;IACRC,WAAW,EAAE,CACX,CAAC,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,EACxC,CAAC,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAC1C;IACDN,IAAI,EAAE;EACR,CAAC;EACDO,EAAE,EAAE;AACN,CAAC,EACD;EACEP,IAAI,EAAE,SAAS;EACfC,UAAU,EAAE;IACVC,WAAW,EAAE,iBAAiB;IAC9BC,uBAAuB,EAAE,UAAU;IACnCC,qBAAqB,EAAE;EACzB,CAAC;EACDC,QAAQ,EAAE;IACRC,WAAW,EAAE,CACX,CACE,CAAC,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,EACxC,CAAC,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,EACxC,CAAC,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,EACxC,CAAC,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,EACvC,CAAC,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,EACxC,CAAC,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,EACvC,CAAC,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CACzC,CACF;IACDN,IAAI,EAAE;EACR,CAAC;EACDO,EAAE,EAAE;AACN,CAAC,EACD;EACEP,IAAI,EAAE,SAAS;EACfC,UAAU,EAAE;IACVC,WAAW,EAAE,kBAAkB;IAC/BC,uBAAuB,EAAE,UAAU;IACnCC,qBAAqB,EAAE;EACzB,CAAC;EACDC,QAAQ,EAAE;IACRC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC;IAC9BN,IAAI,EAAE;EACR,CAAC;EACDO,EAAE,EAAE;AACN,CAAC,CACF;AAED,OAAO,MAAMC,gBAAiC,GAAG,CAC/C;EACER,IAAI,EAAE,SAAS;EACfC,UAAU,EAAE;IACVC,WAAW,EAAE,eAAe;IAC5BC,uBAAuB,EAAE,UAAU;IACnCC,qBAAqB,EAAE;EACzB,CAAC;EACDC,QAAQ,EAAE;IACRC,WAAW,EAAE,CAAC,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IACpDN,IAAI,EAAE;EACR,CAAC;EACDO,EAAE,EAAE;AACN,CAAC,CACF","ignoreList":[]}
@@ -4,7 +4,7 @@ import { ListFormComponent } from '~/src/server/plugins/engine/components/ListFo
4
4
  import * as Components from '~/src/server/plugins/engine/components/index.js';
5
5
  import { type FormState } from '~/src/server/plugins/engine/types.js';
6
6
  export type Component = InstanceType<(typeof Components)[keyof typeof Components]>;
7
- export type Field = InstanceType<typeof Components.AutocompleteField | typeof Components.RadiosField | typeof Components.YesNoField | typeof Components.CheckboxesField | typeof Components.DatePartsField | typeof Components.DeclarationField | typeof Components.EastingNorthingField | typeof Components.EmailAddressField | typeof Components.LatLongField | typeof Components.MonthYearField | typeof Components.MultilineTextField | typeof Components.NationalGridFieldNumberField | typeof Components.NumberField | typeof Components.OsGridRefField | typeof Components.SelectField | typeof Components.TelephoneNumberField | typeof Components.TextField | typeof Components.UkAddressField | typeof Components.FileUploadField | typeof Components.HiddenField | typeof Components.PaymentField>;
7
+ export type Field = InstanceType<typeof Components.AutocompleteField | typeof Components.RadiosField | typeof Components.YesNoField | typeof Components.CheckboxesField | typeof Components.DatePartsField | typeof Components.DeclarationField | typeof Components.EastingNorthingField | typeof Components.EmailAddressField | typeof Components.LatLongField | typeof Components.MonthYearField | typeof Components.MultilineTextField | typeof Components.NationalGridFieldNumberField | typeof Components.NumberField | typeof Components.OsGridRefField | typeof Components.SelectField | typeof Components.TelephoneNumberField | typeof Components.TextField | typeof Components.UkAddressField | typeof Components.FileUploadField | typeof Components.HiddenField | typeof Components.PaymentField | typeof Components.GeospatialField>;
8
8
  export type Guidance = InstanceType<typeof Components.Details> | InstanceType<typeof Components.Html> | InstanceType<typeof Components.Markdown> | InstanceType<typeof Components.InsetText> | InstanceType<typeof Components.List>;
9
9
  export type ListField = InstanceType<typeof Components.AutocompleteField | typeof Components.CheckboxesField | typeof Components.RadiosField | typeof Components.SelectField | typeof Components.YesNoField>;
10
10
  export declare const designerUrl: string;
@@ -113,6 +113,9 @@ export function createComponent(def, options) {
113
113
  case ComponentType.PaymentField:
114
114
  component = new Components.PaymentField(def, options);
115
115
  break;
116
+ case ComponentType.GeospatialField:
117
+ component = new Components.GeospatialField(def, options);
118
+ break;
116
119
  }
117
120
  if (typeof component === 'undefined') {
118
121
  throw new Error(`Component type ${def.type} does not exist`);
@@ -215,6 +218,10 @@ export function getAnswerMarkdown(field, state, options = {
215
218
  } else if (field instanceof Components.EastingNorthingField || field instanceof Components.LatLongField) {
216
219
  const contextValue = field.getContextValueFromState(state);
217
220
  answerEscaped = contextValue ? `${contextValue}\n` : '';
221
+ } else if (field instanceof Components.GeospatialField) {
222
+ const features = field.getFormValueFromState(state);
223
+ const value = field.getDisplayStringFromFormValue(features);
224
+ answerEscaped = value ? `${value}\n` : '';
218
225
  }
219
226
  return answerEscaped;
220
227
  }
@@ -1 +1 @@
1
- {"version":3,"file":"components.js","names":["ComponentType","config","ListFormComponent","escapeMarkdown","Components","markdown","designerUrl","get","hasListFormField","field","type","undefined","isListFieldType","allowedTypes","AutocompleteField","CheckboxesField","RadiosField","SelectField","YesNoField","includes","createComponent","def","options","component","DatePartsField","DeclarationField","Details","EmailAddressField","Html","InsetText","List","Markdown","MultilineTextField","NumberField","TelephoneNumberField","TextField","UkAddressField","MonthYearField","FileUploadField","EastingNorthingField","OsGridRefField","NationalGridFieldNumberField","LatLongField","HiddenField","PaymentField","Error","getAnswer","state","format","getAnswerMarkdown","context","getContextValueFromState","toString","parse","async","trim","getDisplayStringFromState","answer","answerEscaped","files","getFormValueFromState","length","map","status","file","form","filename","fileId","join","values","flat","items","filter","value","item","label","text","line","toLowerCase","split","concat","contextValue"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/components.ts"],"sourcesContent":["import { ComponentType, type ComponentDef } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\nimport { type FormState } from '~/src/server/plugins/engine/types.js'\n\n// All component instances\nexport type Component = InstanceType<\n (typeof Components)[keyof typeof Components]\n>\n\n// Field component instances only\nexport type Field = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.RadiosField\n | typeof Components.YesNoField\n | typeof Components.CheckboxesField\n | typeof Components.DatePartsField\n | typeof Components.DeclarationField\n | typeof Components.EastingNorthingField\n | typeof Components.EmailAddressField\n | typeof Components.LatLongField\n | typeof Components.MonthYearField\n | typeof Components.MultilineTextField\n | typeof Components.NationalGridFieldNumberField\n | typeof Components.NumberField\n | typeof Components.OsGridRefField\n | typeof Components.SelectField\n | typeof Components.TelephoneNumberField\n | typeof Components.TextField\n | typeof Components.UkAddressField\n | typeof Components.FileUploadField\n | typeof Components.HiddenField\n | typeof Components.PaymentField\n>\n\n// Guidance component instances only\nexport type Guidance =\n | InstanceType<typeof Components.Details>\n | InstanceType<typeof Components.Html>\n | InstanceType<typeof Components.Markdown>\n | InstanceType<typeof Components.InsetText>\n | InstanceType<typeof Components.List>\n\n// List component instances only\nexport type ListField = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.CheckboxesField\n | typeof Components.RadiosField\n | typeof Components.SelectField\n | typeof Components.YesNoField\n>\n\nexport const designerUrl = config.get('designerUrl')\n\n// Re-export markdown from its own module to avoid circular dependencies\nexport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\n\n/**\n * Filter known components with lists\n */\nexport function hasListFormField(\n field?: Partial<Component>\n): field is ListFormComponent {\n return !!field && field.type !== undefined && isListFieldType(field.type)\n}\n\nexport function isListFieldType(\n type?: ComponentType\n): type is ListField['type'] {\n const allowedTypes = [\n ComponentType.AutocompleteField,\n ComponentType.CheckboxesField,\n ComponentType.RadiosField,\n ComponentType.SelectField,\n ComponentType.YesNoField\n ]\n\n return !!type && allowedTypes.includes(type)\n}\n\n/**\n * Create field instance for each {@link ComponentDef} type\n */\nexport function createComponent(\n def: ComponentDef,\n options: ConstructorParameters<typeof ComponentBase>[1]\n): Component {\n let component: Component | undefined\n\n switch (def.type) {\n case ComponentType.AutocompleteField:\n component = new Components.AutocompleteField(def, options)\n break\n\n case ComponentType.CheckboxesField:\n component = new Components.CheckboxesField(def, options)\n break\n\n case ComponentType.DatePartsField:\n component = new Components.DatePartsField(def, options)\n break\n\n case ComponentType.DeclarationField:\n component = new Components.DeclarationField(def, options)\n break\n\n case ComponentType.Details:\n component = new Components.Details(def, options)\n break\n\n case ComponentType.EmailAddressField:\n component = new Components.EmailAddressField(def, options)\n break\n\n case ComponentType.Html:\n component = new Components.Html(def, options)\n break\n\n case ComponentType.InsetText:\n component = new Components.InsetText(def, options)\n break\n\n case ComponentType.List:\n component = new Components.List(def, options)\n break\n\n case ComponentType.Markdown:\n component = new Components.Markdown(def, options)\n break\n\n case ComponentType.MultilineTextField:\n component = new Components.MultilineTextField(def, options)\n break\n\n case ComponentType.NumberField:\n component = new Components.NumberField(def, options)\n break\n\n case ComponentType.RadiosField:\n component = new Components.RadiosField(def, options)\n break\n\n case ComponentType.SelectField:\n component = new Components.SelectField(def, options)\n break\n\n case ComponentType.TelephoneNumberField:\n component = new Components.TelephoneNumberField(def, options)\n break\n\n case ComponentType.TextField:\n component = new Components.TextField(def, options)\n break\n\n case ComponentType.UkAddressField:\n component = new Components.UkAddressField(def, options)\n break\n\n case ComponentType.YesNoField:\n component = new Components.YesNoField(def, options)\n break\n\n case ComponentType.MonthYearField:\n component = new Components.MonthYearField(def, options)\n break\n\n case ComponentType.FileUploadField:\n component = new Components.FileUploadField(def, options)\n break\n\n case ComponentType.EastingNorthingField:\n component = new Components.EastingNorthingField(def, options)\n break\n\n case ComponentType.OsGridRefField:\n component = new Components.OsGridRefField(def, options)\n break\n\n case ComponentType.NationalGridFieldNumberField:\n component = new Components.NationalGridFieldNumberField(def, options)\n break\n\n case ComponentType.LatLongField:\n component = new Components.LatLongField(def, options)\n break\n\n case ComponentType.HiddenField:\n component = new Components.HiddenField(def, options)\n break\n\n case ComponentType.PaymentField:\n component = new Components.PaymentField(def, options)\n break\n }\n\n if (typeof component === 'undefined') {\n throw new Error(`Component type ${def.type} does not exist`)\n }\n\n return component\n}\n\n/**\n * Get formatted answer for a field\n */\nexport function getAnswer(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'data' // Submission data\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n // Use escaped display text for GOV.UK Notify emails\n if (options.format === 'email') {\n return getAnswerMarkdown(field, state, { format: 'email' })\n }\n\n // Use context value for submission data\n if (options.format === 'data') {\n const context = field.getContextValueFromState(state)\n return context?.toString() ?? ''\n }\n\n // Use display HTML for check answers summary (multi line)\n if (\n field instanceof ListFormComponent ||\n field instanceof Components.MultilineTextField ||\n field instanceof Components.UkAddressField ||\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n return markdown\n .parse(getAnswerMarkdown(field, state), { async: false })\n .trim()\n }\n\n // Use display text for check answers summary (single line)\n return field.getDisplayStringFromState(state)\n}\n\n/**\n * Get formatted answer for a field (Markdown only)\n */\nexport function getAnswerMarkdown(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n const answer = field.getDisplayStringFromState(state)\n\n // Use escaped display text\n let answerEscaped = `${escapeMarkdown(answer)}\\n`\n\n if (field instanceof Components.FileUploadField) {\n const files = field.getFormValueFromState(state)\n\n // Skip empty files\n if (!files?.length) {\n return answerEscaped\n }\n\n answerEscaped = `${escapeMarkdown(answer)}:\\n\\n`\n\n // Append bullet points\n answerEscaped += files\n .map(({ status }) => {\n const { file } = status.form\n const filename = escapeMarkdown(file.filename)\n return `* [${filename}](${designerUrl}/file-download/${file.fileId})\\n`\n })\n .join('')\n } else if (field instanceof ListFormComponent) {\n const values = [field.getContextValueFromState(state)].flat()\n const items = field.items.filter(({ value }) => values.includes(value))\n\n // Skip empty values\n if (!items.length) {\n return answerEscaped\n }\n\n answerEscaped = ''\n\n // Append bullet points\n answerEscaped += items\n .map((item) => {\n const label = escapeMarkdown(item.text)\n const value = escapeMarkdown(`(${item.value})`)\n\n let line = label\n\n // Prepend bullet points for checkboxes only\n if (field instanceof Components.CheckboxesField) {\n line = `* ${line}`\n }\n\n // Append raw values in parentheses\n // e.g. `* None of the above (false)`\n return options.format === 'email' &&\n `${item.value}`.toLowerCase() !== item.text.toLowerCase()\n ? `${line} ${value}\\n`\n : `${line}\\n`\n })\n .join('')\n } else if (field instanceof Components.MultilineTextField) {\n // Preserve Multiline text new lines\n answerEscaped = answer\n .split(/(?:\\r?\\n)+/)\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (field instanceof Components.UkAddressField) {\n // Format UK addresses into new lines\n answerEscaped = (field.getContextValueFromState(state) ?? [])\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n const contextValue = field.getContextValueFromState(state)\n answerEscaped = contextValue ? `${contextValue}\\n` : ''\n }\n\n return answerEscaped\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"components.js","names":["ComponentType","config","ListFormComponent","escapeMarkdown","Components","markdown","designerUrl","get","hasListFormField","field","type","undefined","isListFieldType","allowedTypes","AutocompleteField","CheckboxesField","RadiosField","SelectField","YesNoField","includes","createComponent","def","options","component","DatePartsField","DeclarationField","Details","EmailAddressField","Html","InsetText","List","Markdown","MultilineTextField","NumberField","TelephoneNumberField","TextField","UkAddressField","MonthYearField","FileUploadField","EastingNorthingField","OsGridRefField","NationalGridFieldNumberField","LatLongField","HiddenField","PaymentField","GeospatialField","Error","getAnswer","state","format","getAnswerMarkdown","context","getContextValueFromState","toString","parse","async","trim","getDisplayStringFromState","answer","answerEscaped","files","getFormValueFromState","length","map","status","file","form","filename","fileId","join","values","flat","items","filter","value","item","label","text","line","toLowerCase","split","concat","contextValue","features","getDisplayStringFromFormValue"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/components.ts"],"sourcesContent":["import { ComponentType, type ComponentDef } from '@defra/forms-model'\n\nimport { config } from '~/src/config/index.js'\nimport { type ComponentBase } from '~/src/server/plugins/engine/components/ComponentBase.js'\nimport { ListFormComponent } from '~/src/server/plugins/engine/components/ListFormComponent.js'\nimport { escapeMarkdown } from '~/src/server/plugins/engine/components/helpers/index.js'\nimport * as Components from '~/src/server/plugins/engine/components/index.js'\nimport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\nimport { type FormState } from '~/src/server/plugins/engine/types.js'\n\n// All component instances\nexport type Component = InstanceType<\n (typeof Components)[keyof typeof Components]\n>\n\n// Field component instances only\nexport type Field = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.RadiosField\n | typeof Components.YesNoField\n | typeof Components.CheckboxesField\n | typeof Components.DatePartsField\n | typeof Components.DeclarationField\n | typeof Components.EastingNorthingField\n | typeof Components.EmailAddressField\n | typeof Components.LatLongField\n | typeof Components.MonthYearField\n | typeof Components.MultilineTextField\n | typeof Components.NationalGridFieldNumberField\n | typeof Components.NumberField\n | typeof Components.OsGridRefField\n | typeof Components.SelectField\n | typeof Components.TelephoneNumberField\n | typeof Components.TextField\n | typeof Components.UkAddressField\n | typeof Components.FileUploadField\n | typeof Components.HiddenField\n | typeof Components.PaymentField\n | typeof Components.GeospatialField\n>\n\n// Guidance component instances only\nexport type Guidance =\n | InstanceType<typeof Components.Details>\n | InstanceType<typeof Components.Html>\n | InstanceType<typeof Components.Markdown>\n | InstanceType<typeof Components.InsetText>\n | InstanceType<typeof Components.List>\n\n// List component instances only\nexport type ListField = InstanceType<\n | typeof Components.AutocompleteField\n | typeof Components.CheckboxesField\n | typeof Components.RadiosField\n | typeof Components.SelectField\n | typeof Components.YesNoField\n>\n\nexport const designerUrl = config.get('designerUrl')\n\n// Re-export markdown from its own module to avoid circular dependencies\nexport { markdown } from '~/src/server/plugins/engine/components/markdownParser.js'\n\n/**\n * Filter known components with lists\n */\nexport function hasListFormField(\n field?: Partial<Component>\n): field is ListFormComponent {\n return !!field && field.type !== undefined && isListFieldType(field.type)\n}\n\nexport function isListFieldType(\n type?: ComponentType\n): type is ListField['type'] {\n const allowedTypes = [\n ComponentType.AutocompleteField,\n ComponentType.CheckboxesField,\n ComponentType.RadiosField,\n ComponentType.SelectField,\n ComponentType.YesNoField\n ]\n\n return !!type && allowedTypes.includes(type)\n}\n\n/**\n * Create field instance for each {@link ComponentDef} type\n */\nexport function createComponent(\n def: ComponentDef,\n options: ConstructorParameters<typeof ComponentBase>[1]\n): Component {\n let component: Component | undefined\n\n switch (def.type) {\n case ComponentType.AutocompleteField:\n component = new Components.AutocompleteField(def, options)\n break\n\n case ComponentType.CheckboxesField:\n component = new Components.CheckboxesField(def, options)\n break\n\n case ComponentType.DatePartsField:\n component = new Components.DatePartsField(def, options)\n break\n\n case ComponentType.DeclarationField:\n component = new Components.DeclarationField(def, options)\n break\n\n case ComponentType.Details:\n component = new Components.Details(def, options)\n break\n\n case ComponentType.EmailAddressField:\n component = new Components.EmailAddressField(def, options)\n break\n\n case ComponentType.Html:\n component = new Components.Html(def, options)\n break\n\n case ComponentType.InsetText:\n component = new Components.InsetText(def, options)\n break\n\n case ComponentType.List:\n component = new Components.List(def, options)\n break\n\n case ComponentType.Markdown:\n component = new Components.Markdown(def, options)\n break\n\n case ComponentType.MultilineTextField:\n component = new Components.MultilineTextField(def, options)\n break\n\n case ComponentType.NumberField:\n component = new Components.NumberField(def, options)\n break\n\n case ComponentType.RadiosField:\n component = new Components.RadiosField(def, options)\n break\n\n case ComponentType.SelectField:\n component = new Components.SelectField(def, options)\n break\n\n case ComponentType.TelephoneNumberField:\n component = new Components.TelephoneNumberField(def, options)\n break\n\n case ComponentType.TextField:\n component = new Components.TextField(def, options)\n break\n\n case ComponentType.UkAddressField:\n component = new Components.UkAddressField(def, options)\n break\n\n case ComponentType.YesNoField:\n component = new Components.YesNoField(def, options)\n break\n\n case ComponentType.MonthYearField:\n component = new Components.MonthYearField(def, options)\n break\n\n case ComponentType.FileUploadField:\n component = new Components.FileUploadField(def, options)\n break\n\n case ComponentType.EastingNorthingField:\n component = new Components.EastingNorthingField(def, options)\n break\n\n case ComponentType.OsGridRefField:\n component = new Components.OsGridRefField(def, options)\n break\n\n case ComponentType.NationalGridFieldNumberField:\n component = new Components.NationalGridFieldNumberField(def, options)\n break\n\n case ComponentType.LatLongField:\n component = new Components.LatLongField(def, options)\n break\n\n case ComponentType.HiddenField:\n component = new Components.HiddenField(def, options)\n break\n\n case ComponentType.PaymentField:\n component = new Components.PaymentField(def, options)\n break\n\n case ComponentType.GeospatialField:\n component = new Components.GeospatialField(def, options)\n break\n }\n\n if (typeof component === 'undefined') {\n throw new Error(`Component type ${def.type} does not exist`)\n }\n\n return component\n}\n\n/**\n * Get formatted answer for a field\n */\nexport function getAnswer(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'data' // Submission data\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n // Use escaped display text for GOV.UK Notify emails\n if (options.format === 'email') {\n return getAnswerMarkdown(field, state, { format: 'email' })\n }\n\n // Use context value for submission data\n if (options.format === 'data') {\n const context = field.getContextValueFromState(state)\n return context?.toString() ?? ''\n }\n\n // Use display HTML for check answers summary (multi line)\n if (\n field instanceof ListFormComponent ||\n field instanceof Components.MultilineTextField ||\n field instanceof Components.UkAddressField ||\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n return markdown\n .parse(getAnswerMarkdown(field, state), { async: false })\n .trim()\n }\n\n // Use display text for check answers summary (single line)\n return field.getDisplayStringFromState(state)\n}\n\n/**\n * Get formatted answer for a field (Markdown only)\n */\nexport function getAnswerMarkdown(\n field: Field,\n state: FormState,\n options: {\n format:\n | 'email' // GOV.UK Notify emails\n | 'summary' // Check answers summary\n } = { format: 'summary' }\n) {\n const answer = field.getDisplayStringFromState(state)\n\n // Use escaped display text\n let answerEscaped = `${escapeMarkdown(answer)}\\n`\n\n if (field instanceof Components.FileUploadField) {\n const files = field.getFormValueFromState(state)\n\n // Skip empty files\n if (!files?.length) {\n return answerEscaped\n }\n\n answerEscaped = `${escapeMarkdown(answer)}:\\n\\n`\n\n // Append bullet points\n answerEscaped += files\n .map(({ status }) => {\n const { file } = status.form\n const filename = escapeMarkdown(file.filename)\n return `* [${filename}](${designerUrl}/file-download/${file.fileId})\\n`\n })\n .join('')\n } else if (field instanceof ListFormComponent) {\n const values = [field.getContextValueFromState(state)].flat()\n const items = field.items.filter(({ value }) => values.includes(value))\n\n // Skip empty values\n if (!items.length) {\n return answerEscaped\n }\n\n answerEscaped = ''\n\n // Append bullet points\n answerEscaped += items\n .map((item) => {\n const label = escapeMarkdown(item.text)\n const value = escapeMarkdown(`(${item.value})`)\n\n let line = label\n\n // Prepend bullet points for checkboxes only\n if (field instanceof Components.CheckboxesField) {\n line = `* ${line}`\n }\n\n // Append raw values in parentheses\n // e.g. `* None of the above (false)`\n return options.format === 'email' &&\n `${item.value}`.toLowerCase() !== item.text.toLowerCase()\n ? `${line} ${value}\\n`\n : `${line}\\n`\n })\n .join('')\n } else if (field instanceof Components.MultilineTextField) {\n // Preserve Multiline text new lines\n answerEscaped = answer\n .split(/(?:\\r?\\n)+/)\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (field instanceof Components.UkAddressField) {\n // Format UK addresses into new lines\n answerEscaped = (field.getContextValueFromState(state) ?? [])\n .map(escapeMarkdown)\n .join('\\n')\n .concat('\\n')\n } else if (\n field instanceof Components.EastingNorthingField ||\n field instanceof Components.LatLongField\n ) {\n const contextValue = field.getContextValueFromState(state)\n answerEscaped = contextValue ? `${contextValue}\\n` : ''\n } else if (field instanceof Components.GeospatialField) {\n const features = field.getFormValueFromState(state)\n const value = field.getDisplayStringFromFormValue(features)\n\n answerEscaped = value ? `${value}\\n` : ''\n }\n\n return answerEscaped\n}\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,6 @@
1
+ import JoiBase from 'joi';
2
+ import { type Feature } from '~/src/server/plugins/engine/types.js';
3
+ export declare const geospatialSchema: JoiBase.ArraySchema<Feature[]>;
4
+ /**
5
+ * @import { CustomHelpers } from 'joi'
6
+ */
@@ -0,0 +1,71 @@
1
+ import Bourne from '@hapi/bourne';
2
+ import JoiBase from 'joi';
3
+ const Joi = JoiBase.extend({
4
+ type: 'array',
5
+ base: JoiBase.array(),
6
+ messages: {
7
+ 'object.invalidjson': '{{#label}} must be a valid json array string'
8
+ },
9
+ coerce: {
10
+ from: 'string',
11
+ method(value, helpers) {
12
+ if (typeof value === 'string') {
13
+ if (value.trim() === '') {
14
+ return {
15
+ value: undefined
16
+ };
17
+ }
18
+ try {
19
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
20
+ return {
21
+ value: Bourne.parse(value)
22
+ };
23
+ } catch {
24
+ const result = {
25
+ value,
26
+ errors: [helpers.error('object.invalidjson')]
27
+ };
28
+ return result;
29
+ }
30
+ } else {
31
+ return {
32
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
33
+ value
34
+ };
35
+ }
36
+ }
37
+ }
38
+ });
39
+ const coordinatesSchema = Joi.array().items(Joi.number().required(), Joi.number().required()).required();
40
+ const featurePropertiesSchema = Joi.object().keys({
41
+ description: Joi.string().required(),
42
+ coordinateGridReference: Joi.string().required(),
43
+ centroidGridReference: Joi.string().required()
44
+ }).required();
45
+ const featureGeometrySchema = Joi.object().keys({
46
+ type: Joi.string().valid('Point', 'LineString', 'Polygon').required(),
47
+ coordinates: Joi.array().when('type', {
48
+ switch: [{
49
+ is: 'Point',
50
+ then: coordinatesSchema
51
+ }, {
52
+ is: 'LineString',
53
+ then: Joi.array().items(coordinatesSchema).min(2)
54
+ }, {
55
+ is: 'Polygon',
56
+ then: Joi.array().items(Joi.array().items(coordinatesSchema).min(3))
57
+ }]
58
+ }).required()
59
+ });
60
+ const featureSchema = Joi.object().keys({
61
+ id: Joi.string().required(),
62
+ type: Joi.string().valid('Feature').required(),
63
+ properties: featurePropertiesSchema,
64
+ geometry: featureGeometrySchema
65
+ });
66
+ export const geospatialSchema = Joi.array().items(featureSchema).unique('id').required();
67
+
68
+ /**
69
+ * @import { CustomHelpers } from 'joi'
70
+ */
71
+ //# sourceMappingURL=geospatial.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geospatial.js","names":["Bourne","JoiBase","Joi","extend","type","base","array","messages","coerce","from","method","value","helpers","trim","undefined","parse","result","errors","error","coordinatesSchema","items","number","required","featurePropertiesSchema","object","keys","description","string","coordinateGridReference","centroidGridReference","featureGeometrySchema","valid","coordinates","when","switch","is","then","min","featureSchema","id","properties","geometry","geospatialSchema","unique"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/geospatial.ts"],"sourcesContent":["import Bourne from '@hapi/bourne'\nimport JoiBase from 'joi'\n\nimport {\n type Coordinates,\n type Feature,\n type FeatureProperties,\n type Geometry\n} from '~/src/server/plugins/engine/types.js'\n\nconst Joi = JoiBase.extend({\n type: 'array',\n base: JoiBase.array(),\n messages: {\n 'object.invalidjson': '{{#label}} must be a valid json array string'\n },\n coerce: {\n from: 'string',\n method(value, helpers) {\n if (typeof value === 'string') {\n if (value.trim() === '') {\n return {\n value: undefined\n }\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n return { value: Bourne.parse(value) }\n } catch {\n const result = {\n value,\n errors: [helpers.error('object.invalidjson')]\n }\n\n return result\n }\n } else {\n return {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n value\n }\n }\n }\n }\n}) as JoiBase.Root\n\nconst coordinatesSchema = Joi.array<Coordinates[]>()\n .items(Joi.number().required(), Joi.number().required())\n .required()\n\nconst featurePropertiesSchema = Joi.object<FeatureProperties>()\n .keys({\n description: Joi.string().required(),\n coordinateGridReference: Joi.string().required(),\n centroidGridReference: Joi.string().required()\n })\n .required()\n\nconst featureGeometrySchema = Joi.object<Geometry>().keys({\n type: Joi.string().valid('Point', 'LineString', 'Polygon').required(),\n coordinates: Joi.array()\n .when('type', {\n switch: [\n { is: 'Point', then: coordinatesSchema },\n {\n is: 'LineString',\n then: Joi.array().items(coordinatesSchema).min(2)\n },\n {\n is: 'Polygon',\n then: Joi.array().items(Joi.array().items(coordinatesSchema).min(3))\n }\n ]\n })\n .required()\n})\n\nconst featureSchema = Joi.object<Feature>().keys({\n id: Joi.string().required(),\n type: Joi.string().valid('Feature').required(),\n properties: featurePropertiesSchema,\n geometry: featureGeometrySchema\n})\n\nexport const geospatialSchema = Joi.array<Feature[]>()\n .items(featureSchema)\n .unique('id')\n .required()\n\n/**\n * @import { CustomHelpers } from 'joi'\n */\n"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,42 @@
1
+ import { validState } from "./__stubs__/geospatial.js";
2
+ import { geospatialSchema } from "./geospatial.js";
3
+ describe('Geospatial validation helpers', () => {
4
+ test('it should not have errors for valid geojson object', () => {
5
+ const result = geospatialSchema.validate(validState);
6
+ expect(result.error).toBeUndefined();
7
+ expect(result.value).toBeDefined();
8
+ expect(result.value).toHaveLength(4);
9
+ });
10
+ test('it should not have errors for valid geojson string', () => {
11
+ const result = geospatialSchema.validate(JSON.stringify(validState));
12
+ expect(result.error).toBeUndefined();
13
+ expect(result.value).toBeDefined();
14
+ expect(result.value).toHaveLength(4);
15
+ });
16
+ test('it should have errors for invalid json string', () => {
17
+ const result = geospatialSchema.validate('{');
18
+ expect(result.error).toBeDefined();
19
+ expect(result.value).toBe('{');
20
+ });
21
+ test('it should have errors for invalid geojson string', () => {
22
+ const result = geospatialSchema.validate('[');
23
+ expect(result.error).toBeDefined();
24
+ expect(result.value).toBe('[');
25
+ });
26
+ test('it should validate an empty array', () => {
27
+ const result = geospatialSchema.validate('[]');
28
+ expect(result.error).toBeUndefined();
29
+ expect(result.value).toEqual([]);
30
+ });
31
+ test('it should not validate an empty object', () => {
32
+ const result = geospatialSchema.validate('{}');
33
+ expect(result.error).toBeDefined();
34
+ expect(result.value).toBeUndefined();
35
+ });
36
+ test('it should validate an empty string', () => {
37
+ const result = geospatialSchema.validate('');
38
+ expect(result.error).toBeDefined();
39
+ expect(result.value).toBeUndefined();
40
+ });
41
+ });
42
+ //# sourceMappingURL=geospatial.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"geospatial.test.js","names":["validState","geospatialSchema","describe","test","result","validate","expect","error","toBeUndefined","value","toBeDefined","toHaveLength","JSON","stringify","toBe","toEqual"],"sources":["../../../../../../src/server/plugins/engine/components/helpers/geospatial.test.js"],"sourcesContent":["import { validState } from '~/src/server/plugins/engine/components/helpers/__stubs__/geospatial.js'\nimport { geospatialSchema } from '~/src/server/plugins/engine/components/helpers/geospatial.js'\n\ndescribe('Geospatial validation helpers', () => {\n test('it should not have errors for valid geojson object', () => {\n const result = geospatialSchema.validate(validState)\n\n expect(result.error).toBeUndefined()\n expect(result.value).toBeDefined()\n expect(result.value).toHaveLength(4)\n })\n\n test('it should not have errors for valid geojson string', () => {\n const result = geospatialSchema.validate(JSON.stringify(validState))\n\n expect(result.error).toBeUndefined()\n expect(result.value).toBeDefined()\n expect(result.value).toHaveLength(4)\n })\n\n test('it should have errors for invalid json string', () => {\n const result = geospatialSchema.validate('{')\n\n expect(result.error).toBeDefined()\n expect(result.value).toBe('{')\n })\n\n test('it should have errors for invalid geojson string', () => {\n const result = geospatialSchema.validate('[')\n\n expect(result.error).toBeDefined()\n expect(result.value).toBe('[')\n })\n\n test('it should validate an empty array', () => {\n const result = geospatialSchema.validate('[]')\n\n expect(result.error).toBeUndefined()\n expect(result.value).toEqual([])\n })\n\n test('it should not validate an empty object', () => {\n const result = geospatialSchema.validate('{}')\n\n expect(result.error).toBeDefined()\n expect(result.value).toBeUndefined()\n })\n\n test('it should validate an empty string', () => {\n const result = geospatialSchema.validate('')\n\n expect(result.error).toBeDefined()\n expect(result.value).toBeUndefined()\n })\n})\n"],"mappings":"AAAA,SAASA,UAAU;AACnB,SAASC,gBAAgB;AAEzBC,QAAQ,CAAC,+BAA+B,EAAE,MAAM;EAC9CC,IAAI,CAAC,oDAAoD,EAAE,MAAM;IAC/D,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAACL,UAAU,CAAC;IAEpDM,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACC,aAAa,CAAC,CAAC;IACpCF,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACC,WAAW,CAAC,CAAC;IAClCJ,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACE,YAAY,CAAC,CAAC,CAAC;EACtC,CAAC,CAAC;EAEFR,IAAI,CAAC,oDAAoD,EAAE,MAAM;IAC/D,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAACO,IAAI,CAACC,SAAS,CAACb,UAAU,CAAC,CAAC;IAEpEM,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACC,aAAa,CAAC,CAAC;IACpCF,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACC,WAAW,CAAC,CAAC;IAClCJ,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACE,YAAY,CAAC,CAAC,CAAC;EACtC,CAAC,CAAC;EAEFR,IAAI,CAAC,+CAA+C,EAAE,MAAM;IAC1D,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAAC,GAAG,CAAC;IAE7CC,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACG,WAAW,CAAC,CAAC;IAClCJ,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACK,IAAI,CAAC,GAAG,CAAC;EAChC,CAAC,CAAC;EAEFX,IAAI,CAAC,kDAAkD,EAAE,MAAM;IAC7D,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAAC,GAAG,CAAC;IAE7CC,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACG,WAAW,CAAC,CAAC;IAClCJ,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACK,IAAI,CAAC,GAAG,CAAC;EAChC,CAAC,CAAC;EAEFX,IAAI,CAAC,mCAAmC,EAAE,MAAM;IAC9C,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAAC,IAAI,CAAC;IAE9CC,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACC,aAAa,CAAC,CAAC;IACpCF,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACM,OAAO,CAAC,EAAE,CAAC;EAClC,CAAC,CAAC;EAEFZ,IAAI,CAAC,wCAAwC,EAAE,MAAM;IACnD,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAAC,IAAI,CAAC;IAE9CC,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACG,WAAW,CAAC,CAAC;IAClCJ,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACD,aAAa,CAAC,CAAC;EACtC,CAAC,CAAC;EAEFL,IAAI,CAAC,oCAAoC,EAAE,MAAM;IAC/C,MAAMC,MAAM,GAAGH,gBAAgB,CAACI,QAAQ,CAAC,EAAE,CAAC;IAE5CC,MAAM,CAACF,MAAM,CAACG,KAAK,CAAC,CAACG,WAAW,CAAC,CAAC;IAClCJ,MAAM,CAACF,MAAM,CAACK,KAAK,CAAC,CAACD,aAAa,CAAC,CAAC;EACtC,CAAC,CAAC;AACJ,CAAC,CAAC","ignoreList":[]}
@@ -29,3 +29,4 @@ export { NationalGridFieldNumberField } from '~/src/server/plugins/engine/compon
29
29
  export { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js';
30
30
  export { HiddenField } from '~/src/server/plugins/engine/components/HiddenField.js';
31
31
  export { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js';
32
+ export { GeospatialField } from '~/src/server/plugins/engine/components/GeospatialField.js';
@@ -30,4 +30,5 @@ export { NationalGridFieldNumberField } from "./NationalGridFieldNumberField.js"
30
30
  export { LatLongField } from "./LatLongField.js";
31
31
  export { HiddenField } from "./HiddenField.js";
32
32
  export { PaymentField } from "./PaymentField.js";
33
+ export { GeospatialField } from "./GeospatialField.js";
33
34
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["AutocompleteField","CheckboxesField","DatePartsField","DeclarationField","Details","EmailAddressField","FileUploadField","Html","InsetText","List","Markdown","MonthYearField","MultilineTextField","NumberField","RadiosField","SelectField","TelephoneNumberField","TextField","UkAddressField","YesNoField","EastingNorthingField","OsGridRefField","NationalGridFieldNumberField","LatLongField","HiddenField","PaymentField"],"sources":["../../../../../src/server/plugins/engine/components/index.ts"],"sourcesContent":["/**\n * IMPORTANT: Exported Components must follow the naming convention implemented in {@link @defra/forms-model#ComponentType}\n * In the Form JSON, components have a type property which is the name of the components, e.g. DatePartsField.\n * Components are loaded in the ComponentsCollection constructor.\n */\n\nexport { AutocompleteField } from '~/src/server/plugins/engine/components/AutocompleteField.js'\nexport { CheckboxesField } from '~/src/server/plugins/engine/components/CheckboxesField.js'\nexport { DatePartsField } from '~/src/server/plugins/engine/components/DatePartsField.js'\nexport { DeclarationField } from '~/src/server/plugins/engine/components/DeclarationField.js'\nexport { Details } from '~/src/server/plugins/engine/components/Details.js'\nexport { EmailAddressField } from '~/src/server/plugins/engine/components/EmailAddressField.js'\nexport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nexport { Html } from '~/src/server/plugins/engine/components/Html.js'\nexport { InsetText } from '~/src/server/plugins/engine/components/InsetText.js'\nexport { List } from '~/src/server/plugins/engine/components/List.js'\nexport { Markdown } from '~/src/server/plugins/engine/components/Markdown.js'\nexport { MonthYearField } from '~/src/server/plugins/engine/components/MonthYearField.js'\nexport { MultilineTextField } from '~/src/server/plugins/engine/components/MultilineTextField.js'\nexport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nexport { RadiosField } from '~/src/server/plugins/engine/components/RadiosField.js'\nexport { SelectField } from '~/src/server/plugins/engine/components/SelectField.js'\nexport { TelephoneNumberField } from '~/src/server/plugins/engine/components/TelephoneNumberField.js'\nexport { TextField } from '~/src/server/plugins/engine/components/TextField.js'\nexport { UkAddressField } from '~/src/server/plugins/engine/components/UkAddressField.js'\nexport { YesNoField } from '~/src/server/plugins/engine/components/YesNoField.js'\nexport { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nexport { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'\nexport { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'\nexport { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nexport { HiddenField } from '~/src/server/plugins/engine/components/HiddenField.js'\nexport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA,SAASA,iBAAiB;AAC1B,SAASC,eAAe;AACxB,SAASC,cAAc;AACvB,SAASC,gBAAgB;AACzB,SAASC,OAAO;AAChB,SAASC,iBAAiB;AAC1B,SAASC,eAAe;AACxB,SAASC,IAAI;AACb,SAASC,SAAS;AAClB,SAASC,IAAI;AACb,SAASC,QAAQ;AACjB,SAASC,cAAc;AACvB,SAASC,kBAAkB;AAC3B,SAASC,WAAW;AACpB,SAASC,WAAW;AACpB,SAASC,WAAW;AACpB,SAASC,oBAAoB;AAC7B,SAASC,SAAS;AAClB,SAASC,cAAc;AACvB,SAASC,UAAU;AACnB,SAASC,oBAAoB;AAC7B,SAASC,cAAc;AACvB,SAASC,4BAA4B;AACrC,SAASC,YAAY;AACrB,SAASC,WAAW;AACpB,SAASC,YAAY","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["AutocompleteField","CheckboxesField","DatePartsField","DeclarationField","Details","EmailAddressField","FileUploadField","Html","InsetText","List","Markdown","MonthYearField","MultilineTextField","NumberField","RadiosField","SelectField","TelephoneNumberField","TextField","UkAddressField","YesNoField","EastingNorthingField","OsGridRefField","NationalGridFieldNumberField","LatLongField","HiddenField","PaymentField","GeospatialField"],"sources":["../../../../../src/server/plugins/engine/components/index.ts"],"sourcesContent":["/**\n * IMPORTANT: Exported Components must follow the naming convention implemented in {@link @defra/forms-model#ComponentType}\n * In the Form JSON, components have a type property which is the name of the components, e.g. DatePartsField.\n * Components are loaded in the ComponentsCollection constructor.\n */\n\nexport { AutocompleteField } from '~/src/server/plugins/engine/components/AutocompleteField.js'\nexport { CheckboxesField } from '~/src/server/plugins/engine/components/CheckboxesField.js'\nexport { DatePartsField } from '~/src/server/plugins/engine/components/DatePartsField.js'\nexport { DeclarationField } from '~/src/server/plugins/engine/components/DeclarationField.js'\nexport { Details } from '~/src/server/plugins/engine/components/Details.js'\nexport { EmailAddressField } from '~/src/server/plugins/engine/components/EmailAddressField.js'\nexport { FileUploadField } from '~/src/server/plugins/engine/components/FileUploadField.js'\nexport { Html } from '~/src/server/plugins/engine/components/Html.js'\nexport { InsetText } from '~/src/server/plugins/engine/components/InsetText.js'\nexport { List } from '~/src/server/plugins/engine/components/List.js'\nexport { Markdown } from '~/src/server/plugins/engine/components/Markdown.js'\nexport { MonthYearField } from '~/src/server/plugins/engine/components/MonthYearField.js'\nexport { MultilineTextField } from '~/src/server/plugins/engine/components/MultilineTextField.js'\nexport { NumberField } from '~/src/server/plugins/engine/components/NumberField.js'\nexport { RadiosField } from '~/src/server/plugins/engine/components/RadiosField.js'\nexport { SelectField } from '~/src/server/plugins/engine/components/SelectField.js'\nexport { TelephoneNumberField } from '~/src/server/plugins/engine/components/TelephoneNumberField.js'\nexport { TextField } from '~/src/server/plugins/engine/components/TextField.js'\nexport { UkAddressField } from '~/src/server/plugins/engine/components/UkAddressField.js'\nexport { YesNoField } from '~/src/server/plugins/engine/components/YesNoField.js'\nexport { EastingNorthingField } from '~/src/server/plugins/engine/components/EastingNorthingField.js'\nexport { OsGridRefField } from '~/src/server/plugins/engine/components/OsGridRefField.js'\nexport { NationalGridFieldNumberField } from '~/src/server/plugins/engine/components/NationalGridFieldNumberField.js'\nexport { LatLongField } from '~/src/server/plugins/engine/components/LatLongField.js'\nexport { HiddenField } from '~/src/server/plugins/engine/components/HiddenField.js'\nexport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nexport { GeospatialField } from '~/src/server/plugins/engine/components/GeospatialField.js'\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;;AAEA,SAASA,iBAAiB;AAC1B,SAASC,eAAe;AACxB,SAASC,cAAc;AACvB,SAASC,gBAAgB;AACzB,SAASC,OAAO;AAChB,SAASC,iBAAiB;AAC1B,SAASC,eAAe;AACxB,SAASC,IAAI;AACb,SAASC,SAAS;AAClB,SAASC,IAAI;AACb,SAASC,QAAQ;AACjB,SAASC,cAAc;AACvB,SAASC,kBAAkB;AAC3B,SAASC,WAAW;AACpB,SAASC,WAAW;AACpB,SAASC,WAAW;AACpB,SAASC,oBAAoB;AAC7B,SAASC,SAAS;AAClB,SAASC,cAAc;AACvB,SAASC,UAAU;AACnB,SAASC,oBAAoB;AAC7B,SAASC,cAAc;AACvB,SAASC,4BAA4B;AACrC,SAASC,YAAY;AACrB,SAASC,WAAW;AACpB,SAASC,YAAY;AACrB,SAASC,eAAe","ignoreList":[]}
@@ -52,6 +52,17 @@ export declare class FileUploadPageController extends QuestionPageController {
52
52
  * @param depth - the number of retries so far
53
53
  */
54
54
  private checkUploadStatus;
55
+ /**
56
+ * Processes the uploaded files from a CDP status response.
57
+ * Complete files are added to state, rejected/pending files
58
+ * have their error messages flashed.
59
+ * @param request - the hapi request
60
+ * @param state - the form state
61
+ * @param validatedItem - the Joi-validated upload item
62
+ * @param files - the current files array from state
63
+ * @param upload - the current upload initiation response
64
+ */
65
+ private processUploadedFiles;
55
66
  /**
56
67
  * Checks the payload for a file getting removed
57
68
  * and removes it from the upload files if found
@@ -125,8 +125,8 @@ export class FileUploadPageController extends QuestionPageController {
125
125
  } = context;
126
126
  const files = this.getFilesFromState(state);
127
127
  const fileToRemove = files.find(({
128
- uploadId
129
- }) => uploadId === params.itemId);
128
+ status
129
+ }) => status.form.file.fileId === params.itemId);
130
130
  if (!fileToRemove) {
131
131
  throw Boom.notFound('File to delete not found');
132
132
  }
@@ -309,8 +309,7 @@ export class FileUploadPageController extends QuestionPageController {
309
309
 
310
310
  // Only add to files state if the file validates.
311
311
  // This secures against html tampering of the file input
312
- // by adding a 'multiple' attribute or it being
313
- // changed to a simple text field or similar.
312
+ // (e.g. changing it to a simple text field or similar).
314
313
  const validationResult = tempItemSchema.validate({
315
314
  uploadId,
316
315
  status: statusResponse
@@ -318,13 +317,69 @@ export class FileUploadPageController extends QuestionPageController {
318
317
  stripUnknown: true
319
318
  });
320
319
  const error = validationResult.error;
321
- const fileState = validationResult.value;
322
320
  if (error) {
323
321
  return this.initiateAndStoreNewUpload(request, state);
324
322
  }
325
- const file = fileState.status.form.file;
326
- if (file.fileStatus === FileStatus.complete) {
327
- files.unshift(prepareFileState(fileState));
323
+
324
+ // CDP returns form.file as a single object for one file,
325
+ // or an array for multiple files. The Joi schema normalises
326
+ // both to an array via .single().
327
+ await this.processUploadedFiles(request, state, validationResult.value, files, upload);
328
+ return this.initiateAndStoreNewUpload(request, state);
329
+ }
330
+
331
+ /**
332
+ * Processes the uploaded files from a CDP status response.
333
+ * Complete files are added to state, rejected/pending files
334
+ * have their error messages flashed.
335
+ * @param request - the hapi request
336
+ * @param state - the form state
337
+ * @param validatedItem - the Joi-validated upload item
338
+ * @param files - the current files array from state
339
+ * @param upload - the current upload initiation response
340
+ */
341
+ async processUploadedFiles(request, state, validatedItem, files, upload) {
342
+ const {
343
+ uploadId
344
+ } = validatedItem;
345
+ const validatedStatus = validatedItem.status;
346
+ const rawFile = validatedStatus.form.file;
347
+ const uploadedFiles = Array.isArray(rawFile) ? rawFile : [rawFile];
348
+ const allErrors = [];
349
+ for (const file of uploadedFiles) {
350
+ if (file.fileStatus === FileStatus.complete) {
351
+ const perFileState = {
352
+ uploadId,
353
+ status: {
354
+ ...validatedStatus,
355
+ form: {
356
+ file
357
+ }
358
+ }
359
+ };
360
+ files.unshift(prepareFileState(perFileState));
361
+ } else {
362
+ // Collect the error for rejected/pending files.
363
+ const {
364
+ fileUpload
365
+ } = this;
366
+ const name = fileUpload.name;
367
+ const text = file.errorMessage ?? 'Unknown error';
368
+ allErrors.push({
369
+ path: [name],
370
+ href: `#${name}`,
371
+ name,
372
+ text
373
+ });
374
+ }
375
+ }
376
+ if (allErrors.length) {
377
+ const cacheService = getCacheService(request.server);
378
+ cacheService.setFlash(request, {
379
+ errors: allErrors
380
+ });
381
+ }
382
+ if (uploadedFiles.some(f => f.fileStatus === FileStatus.complete)) {
328
383
  await this.mergeState(request, state, {
329
384
  upload: {
330
385
  [this.path]: {
@@ -333,25 +388,7 @@ export class FileUploadPageController extends QuestionPageController {
333
388
  }
334
389
  }
335
390
  });
336
- } else {
337
- // Flash the error message.
338
- const {
339
- fileUpload
340
- } = this;
341
- const cacheService = getCacheService(request.server);
342
- const name = fileUpload.name;
343
- const text = file.errorMessage ?? 'Unknown error';
344
- const errors = [{
345
- path: [name],
346
- href: `#${name}`,
347
- name,
348
- text
349
- }];
350
- cacheService.setFlash(request, {
351
- errors
352
- });
353
391
  }
354
- return this.initiateAndStoreNewUpload(request, state);
355
392
  }
356
393
 
357
394
  /**
@@ -371,8 +408,8 @@ export class FileUploadPageController extends QuestionPageController {
371
408
  const upload = this.getUploadFromState(state);
372
409
  const files = this.getFilesFromState(state);
373
410
  const filesUpdated = files.filter(({
374
- uploadId
375
- }) => uploadId !== params.itemId);
411
+ status
412
+ }) => status.form.file.fileId !== params.itemId);
376
413
  if (filesUpdated.length === files.length) {
377
414
  return;
378
415
  }