@datagouv/components-next 1.0.2-dev.0 → 1.0.2-dev.1

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.
@@ -1,4 +1,4 @@
1
- import { b as Ke } from "./main-CrSRA2X-.js";
1
+ import { b as Ke } from "./main-B2kXxWRG.js";
2
2
  import We from "vue";
3
3
  function Fe(I, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.0",
3
+ "version": "1.0.2-dev.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -228,6 +228,52 @@
228
228
  />
229
229
  </dd>
230
230
  </template>
231
+ <template v-if="wfsFormats.length">
232
+ <dt class="font-bold fr-text--sm fr-mb-0">
233
+ <div class="flex gap-1 items-center">
234
+ {{ t('Formats exportés depuis le service WFS') }}
235
+ <span v-if="defaultWfsProjection"> ({{ t('projection {crs}', { crs: defaultWfsProjection }) }})</span>
236
+ <Tooltip>
237
+ <RiInformationLine
238
+ class="flex-none size-4"
239
+ :aria-label="t(`Le lien de téléchargement interroge directement le flux WFS distant. Le nombre de features téléchargées peut être limité.`)"
240
+ aria-hidden="true"
241
+ />
242
+ <template #tooltip>
243
+ <p class="text-sm font-normal mb-0">
244
+ {{ t(`Le lien de téléchargement interroge directement le flux WFS distant.`) }}
245
+ </p>
246
+ <p class="text-sm font-normal mb-0">
247
+ {{ t(`Le nombre de features téléchargées peut être limité.`) }}
248
+ </p>
249
+ </template>
250
+ </Tooltip>
251
+ </div>
252
+ </dt>
253
+ <dd
254
+ v-for="wfsFormat in wfsFormats"
255
+ :key="wfsFormat.format"
256
+ class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
257
+ >
258
+ <span>
259
+ <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
260
+ <a
261
+ :href="wfsFormat.url"
262
+ class="fr-link"
263
+ rel="ugc nofollow noopener"
264
+ @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${wfsFormat.format}`)"
265
+ >
266
+ <span>{{ t('Format {format}', { format: wfsFormat.format }) }}</span>
267
+ </a>
268
+ </span>
269
+ <CopyButton
270
+ :label="t('Copier le lien')"
271
+ :copied-label="t('Lien copié !')"
272
+ :text="wfsFormat.url"
273
+ class="relative"
274
+ />
275
+ </dd>
276
+ </template>
231
277
  </dl>
232
278
  </div>
233
279
  <div v-if="tab.key === 'swagger'">
@@ -251,7 +297,7 @@
251
297
 
252
298
  <script setup lang="ts">
253
299
  import { computed, defineAsyncComponent } from 'vue'
254
- import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiSubtractLine } from '@remixicon/vue'
300
+ import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiInformationLine, RiSubtractLine } from '@remixicon/vue'
255
301
  import { toast } from 'vue-sonner'
256
302
  import BrandedButton from '../BrandedButton.vue'
257
303
  import CopyButton from '../CopyButton.vue'
@@ -263,6 +309,7 @@ import TabList from '../Tabs/TabList.vue'
263
309
  import Tab from '../Tabs/Tab.vue'
264
310
  import TabPanels from '../Tabs/TabPanels.vue'
265
311
  import TabPanel from '../Tabs/TabPanel.vue'
312
+ import Tooltip from '../Tooltip.vue'
266
313
  import Preview from '../ResourceAccordion/Preview.vue'
267
314
  import DataStructure from '../ResourceAccordion/DataStructure.vue'
268
315
  import Metadata from '../ResourceAccordion/Metadata.vue'
@@ -316,6 +363,8 @@ const {
316
363
  ogcService,
317
364
  ogcWms,
318
365
  generatedFormats,
366
+ wfsFormats,
367
+ defaultWfsProjection,
319
368
  isResourceUrl,
320
369
  tabsOptions,
321
370
  } = useResourceCapabilities(() => props.resource, () => props.dataset)
@@ -4,8 +4,9 @@ import { useTranslation } from './useTranslation'
4
4
  import { useHasTabularData } from './useHasTabularData'
5
5
  import { detectOgcService } from '../functions/resources'
6
6
  import { isOrganizationCertified } from '../functions/organizations'
7
- import type { Resource } from '../types/resources'
7
+ import type { Resource, WfsMetadata } from '../types/resources'
8
8
  import type { Dataset, DatasetV2 } from '../types/datasets'
9
+ import { getWfsExportFormats } from '../functions/resourceCapabilities'
9
10
 
10
11
  const GENERATED_FORMATS = ['parquet', 'pmtiles', 'geojson']
11
12
  const URL_FORMATS = ['url', 'doi', 'www:link', 'www:link-1.0-http--link', 'www:link-1.0-http--partners', 'www:link-1.0-http--related', 'www:link-1.0-http--samples']
@@ -67,6 +68,17 @@ export function useResourceCapabilities(
67
68
  return formats
68
69
  })
69
70
 
71
+ const wfsFormats = computed(() => {
72
+ return getWfsExportFormats(toValue(resource))
73
+ })
74
+
75
+ const defaultWfsProjection = computed<string | null>(() => {
76
+ const r = toValue(resource)
77
+ const wfsMetadata = r.extras['analysis:parsing:ogc_metadata'] as WfsMetadata | null
78
+ if (!wfsMetadata || wfsMetadata.format !== `wfs`) return null
79
+ return wfsMetadata?.detected_layer?.default_crs ?? null
80
+ })
81
+
70
82
  const isResourceUrl = computed(() => URL_FORMATS.includes(toValue(resource).format))
71
83
 
72
84
  const tabsOptions = computed(() => {
@@ -111,6 +123,8 @@ export function useResourceCapabilities(
111
123
  ogcService,
112
124
  ogcWms,
113
125
  generatedFormats,
126
+ wfsFormats,
127
+ defaultWfsProjection,
114
128
  isResourceUrl,
115
129
  tabsOptions,
116
130
  }
@@ -1,9 +1,17 @@
1
- import { ref, toValue, watchEffect, onMounted, type ComputedRef, type Ref } from 'vue'
1
+ import { ref, toValue, watchEffect, onMounted, type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue'
2
2
  import { ofetch } from 'ofetch'
3
3
  import { useComponentsConfig } from '../config'
4
4
  import { useTranslation } from '../composables/useTranslation'
5
5
  import type { AsyncData, AsyncDataRequestStatus, UseFetchOptions } from './api.types'
6
6
 
7
+ function deepToValue(obj: MaybeRefOrGetter<Record<string, unknown> | undefined>): Record<string, unknown> | undefined {
8
+ const val = toValue(obj)
9
+ if (!val || typeof val !== 'object') return val
10
+ return Object.fromEntries(
11
+ Object.entries(val).map(([k, v]) => [k, toValue(v as MaybeRefOrGetter<unknown>)]),
12
+ )
13
+ }
14
+
7
15
  export async function useFetch<DataT, ErrorT = never>(
8
16
  url: string | Request | Ref<string | Request> | ComputedRef<string | null> | (() => string | Request),
9
17
  options?: UseFetchOptions<DataT>,
@@ -23,8 +31,8 @@ export async function useFetch<DataT, ErrorT = never>(
23
31
  const execute = async () => {
24
32
  const urlValue = toValue(url)
25
33
  if (!urlValue) return
26
- const params = toValue(options?.params)
27
- const query = toValue(options?.query)
34
+ const params = deepToValue(options?.params)
35
+ const query = deepToValue(options?.query)
28
36
  status.value = 'pending'
29
37
  try {
30
38
  data.value = await ofetch<DataT | null>(urlValue, {
@@ -0,0 +1,55 @@
1
+ import type { Resource, WfsMetadata, OgcLayerInfo } from '../types/resources'
2
+
3
+ const WFS_EXPORT_FORMATS = [
4
+ {
5
+ name: 'csv',
6
+ mimetype: 'csv',
7
+ },
8
+ {
9
+ name: 'json',
10
+ mimetype: 'application/json',
11
+ },
12
+ {
13
+ name: 'shp',
14
+ mimetype: 'SHAPE-ZIP',
15
+ },
16
+ {
17
+ name: 'gml',
18
+ mimetype: 'application/gml+xml',
19
+ },
20
+ {
21
+ name: 'kml',
22
+ mimetype: 'KML',
23
+ },
24
+ {
25
+ name: 'gpkg',
26
+ mimetype: 'application/geopackage+sqlite3',
27
+ },
28
+ ]
29
+
30
+ function buildWfsDownloadUrl(baseUrl: string, wfsMetadata: WfsMetadata, format: { name: string, mimetype: string }, layer: OgcLayerInfo) {
31
+ const version = wfsMetadata.version
32
+ const query = new URLSearchParams({
33
+ SERVICE: 'WFS',
34
+ REQUEST: 'GetFeature',
35
+ VERSION: version,
36
+ ...(Number(version.split('.')[0]) >= 2 ? { TYPENAMES: layer.name } : { TYPENAME: layer.name }),
37
+ OUTPUTFORMAT: format.mimetype,
38
+ ...(layer.default_crs ? { SRSNAME: layer.default_crs } : {}),
39
+ })
40
+ return `${baseUrl.split('?')[0]}?${query.toString()}`
41
+ }
42
+
43
+ export function getWfsExportFormats(resource: Pick<Resource, 'extras' | 'url'>) {
44
+ const wfsMetadata = resource.extras['analysis:parsing:ogc_metadata'] as WfsMetadata | null
45
+ if (!wfsMetadata || wfsMetadata.format !== `wfs`) return []
46
+ const outputFormats = wfsMetadata.output_formats.map((format: string) => format.toLowerCase())
47
+ const layer = wfsMetadata.detected_layer
48
+ if (!layer) return []
49
+ const formats = WFS_EXPORT_FORMATS.filter(format => outputFormats.includes(format.mimetype.toLowerCase()))
50
+ .map(format => ({
51
+ url: buildWfsDownloadUrl(resource.url, wfsMetadata, format, layer),
52
+ format: format.name,
53
+ }))
54
+ return formats
55
+ }
@@ -41,3 +41,13 @@ export interface ResourceGroup {
41
41
  total: number
42
42
  items: Resource[]
43
43
  }
44
+
45
+ export type OgcLayerInfo = { name: string, default_crs: string | null }
46
+
47
+ export type WfsMetadata = {
48
+ format: string
49
+ layers: Array<OgcLayerInfo>
50
+ version: string
51
+ detected_layer: OgcLayerInfo | null
52
+ output_formats: Array<string>
53
+ }
@@ -1,4 +0,0 @@
1
- import { i as f } from "./main-CrSRA2X-.js";
2
- export {
3
- f as default
4
- };