@datagouv/components-next 1.2.0 → 1.4.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 (138) hide show
  1. package/README.md +7 -6
  2. package/dist/{Control-ZFh5ta_U.js → Control-B9WQxlpJ.js} +1 -1
  3. package/dist/{Datafair.client-rf4T1IkA.js → Datafair.client-D_6yzJUG.js} +9 -9
  4. package/dist/{Event-DSQcW7OF.js → Event-iXDrp-fy.js} +11 -11
  5. package/dist/{Image-BijNEG0p.js → Image-mNMBOofs.js} +5 -5
  6. package/dist/ImagePreview.client-B0iPhFm3.js +36 -0
  7. package/dist/{JsonPreview.client-dzar6iuh.js → JsonPreview.client-DSl1kiwC.js} +5 -5
  8. package/dist/{Map-BUtPf5GN.js → Map-BfqKMw0S.js} +1306 -1166
  9. package/dist/{MapContainer.client-D-MoRNhG.js → MapContainer.client-BSfpk_rT.js} +15 -15
  10. package/dist/{OSM-D4MTdBtk.js → OSM-CcrUhItr.js} +15 -23
  11. package/dist/{PdfPreview.client-DoDYLmJD.js → PdfPreview.client-BANFeM6K.js} +441 -434
  12. package/dist/Pmtiles.client-DH9ZrsZR.js +26333 -0
  13. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-I8EZCgzp.js +60 -0
  14. package/dist/{ScaleLine-hJQIqcZm.js → ScaleLine-VGQ7nkMS.js} +2 -2
  15. package/dist/{Tile-Dcl7oIVu.js → Tile-CSpn0Ca8.js} +153 -156
  16. package/dist/{TileImage-BJeHipMX.js → TileImage-CpbaEFwd.js} +143 -131
  17. package/dist/{View-xp_P_OHw.js → View-BYBpt-AU.js} +167 -117
  18. package/dist/{XmlPreview.client-cOhwff6P.js → XmlPreview.client-KCB5mwVf.js} +5 -5
  19. package/dist/{common-BjQlan3k.js → common-BoeTUnDZ.js} +26 -26
  20. package/dist/components-next.css +1 -52
  21. package/dist/components-next.js +201 -170
  22. package/dist/components.css +2 -2
  23. package/dist/{index-NofRBuyf.js → index-DHWzX5vK.js} +5999 -5472
  24. package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-pVQfonBI.js} +162 -164
  25. package/dist/{main-Iz1ZCL6k.js → main-BDcYkhOc.js} +36671 -29402
  26. package/dist/{proj-CsNo9yH1.js → proj-DxGbobp7.js} +312 -296
  27. package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
  28. package/dist/{tilecoord-A0fLnBZr.js → tilecoord-DvX7WWiA.js} +264 -255
  29. package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
  30. package/dist/{vue3-xml-viewer.common-tVI9uXUz.js → vue3-xml-viewer.common-Bdcn54ST.js} +1 -1
  31. package/package.json +40 -39
  32. package/src/components/ActivityList/ActivityList.vue +21 -12
  33. package/src/components/AppLink.vue +3 -0
  34. package/src/components/BannerAction.vue +6 -2
  35. package/src/components/Chart/ChartViewer.vue +111 -17
  36. package/src/components/Chart/ChartViewerWrapper.vue +28 -16
  37. package/src/components/ChartCard.vue +112 -0
  38. package/src/components/CopyButton.vue +6 -2
  39. package/src/components/DataserviceCard.vue +23 -6
  40. package/src/components/DataserviceQuality.vue +82 -0
  41. package/src/components/DataserviceQualityInline.vue +31 -0
  42. package/src/components/DataserviceQualityTooltipContent.vue +79 -0
  43. package/src/components/DatasetCard.vue +17 -6
  44. package/src/components/DatasetInformation/DatasetTemporalitySection.vue +7 -4
  45. package/src/components/DatasetQuality.vue +11 -11
  46. package/src/components/DatasetQualityInline.vue +2 -2
  47. package/src/components/DatasetQualityTooltipContent.vue +1 -1
  48. package/src/components/DiscussionMessageCard.vue +14 -15
  49. package/src/components/Form/SchemaSelect.vue +2 -0
  50. package/src/components/FormattedDate.vue +69 -0
  51. package/src/components/LoadingBlock.vue +4 -1
  52. package/src/components/ObjectCard.vue +4 -1
  53. package/src/components/ObjectCardOwner.vue +13 -1
  54. package/src/components/Pagination.vue +18 -58
  55. package/src/components/Placeholder.vue +3 -2
  56. package/src/components/PostCard.vue +13 -15
  57. package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
  58. package/src/components/ResourceAccordion/Downloads.vue +19 -7
  59. package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
  60. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -8
  61. package/src/components/ResourceAccordion/Metadata.vue +21 -5
  62. package/src/components/ResourceAccordion/PdfPreview.client.vue +36 -12
  63. package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
  64. package/src/components/ResourceAccordion/Pmtiles.client.vue +30 -19
  65. package/src/components/ResourceAccordion/Preview.vue +8 -4
  66. package/src/components/ResourceAccordion/PreviewWrapper.vue +17 -10
  67. package/src/components/ResourceAccordion/ResourceAccordion.vue +31 -11
  68. package/src/components/ResourceExplorer/ResourceExplorer.vue +105 -210
  69. package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +79 -0
  70. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +104 -71
  71. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +140 -184
  72. package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
  73. package/src/components/ResourceExplorer/ResourceSelector.vue +24 -86
  74. package/src/components/ResourceExplorer/ResourceViewerHeader.vue +124 -0
  75. package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +96 -0
  76. package/src/components/ResourceListItem.vue +168 -0
  77. package/src/components/ReuseCard.vue +18 -6
  78. package/src/components/ReuseDetails.vue +2 -2
  79. package/src/components/ReuseHorizontalCard.vue +4 -3
  80. package/src/components/SchemaCard.vue +10 -1
  81. package/src/components/Search/Filter/AccessTypeFilter.vue +2 -2
  82. package/src/components/Search/GlobalSearch.vue +28 -10
  83. package/src/components/Search/SearchInput.vue +1 -1
  84. package/src/components/Search/Sidemenu.vue +4 -3
  85. package/src/components/SmallChart.vue +11 -10
  86. package/src/components/StatBox.vue +13 -5
  87. package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
  88. package/src/components/TabularExplorer/TabularCell.vue +7 -8
  89. package/src/components/TabularExplorer/TabularCellPopover.vue +21 -5
  90. package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
  91. package/src/components/TabularExplorer/TabularDateField.vue +41 -0
  92. package/src/components/TabularExplorer/TabularDateFilter.vue +403 -0
  93. package/src/components/TabularExplorer/TabularExplorer.vue +127 -780
  94. package/src/components/TabularExplorer/TabularFilterContent.vue +48 -38
  95. package/src/components/TabularExplorer/TabularFilterPopover.vue +27 -32
  96. package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
  97. package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
  98. package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
  99. package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
  100. package/src/components/TabularExplorer/TabularTable.vue +484 -0
  101. package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
  102. package/src/components/TabularExplorer/types.ts +40 -28
  103. package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
  104. package/src/components/TabularExplorer/useTabularContext.ts +82 -0
  105. package/src/components/TopicCard.vue +15 -14
  106. package/src/composables/useActiveDescendant.ts +5 -0
  107. package/src/composables/useDatasetResources.ts +164 -0
  108. package/src/composables/useHasTabularData.ts +2 -1
  109. package/src/composables/useResizable.ts +46 -0
  110. package/src/composables/useResourceCapabilities.ts +48 -40
  111. package/src/composables/useSearchFilter.ts +7 -5
  112. package/src/composables/useTranslation.ts +24 -9
  113. package/src/config.ts +1 -0
  114. package/src/functions/api.ts +13 -7
  115. package/src/functions/charts.ts +32 -1
  116. package/src/functions/dates.ts +26 -7
  117. package/src/functions/helpers.ts +15 -0
  118. package/src/functions/metrics.ts +57 -56
  119. package/src/functions/owned.ts +21 -0
  120. package/src/functions/paginate.ts +46 -0
  121. package/src/functions/resourceCapabilities.ts +34 -0
  122. package/src/functions/resources.ts +88 -0
  123. package/src/functions/schemas.ts +27 -0
  124. package/src/functions/scroll.ts +20 -0
  125. package/src/functions/tabular.ts +222 -5
  126. package/src/functions/tabularApi.ts +1 -43
  127. package/src/main.ts +32 -6
  128. package/src/types/dataservices.ts +15 -0
  129. package/src/types/organizations.ts +10 -0
  130. package/src/types/resources.ts +0 -1
  131. package/src/types/reuses.ts +12 -1
  132. package/src/types/search.ts +2 -2
  133. package/src/types/visualizations.ts +5 -0
  134. package/dist/Pmtiles.client-Dzm01Zfm.js +0 -25185
  135. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BRNYswg3.js +0 -61
  136. /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
  137. /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
  138. /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
@@ -0,0 +1,44 @@
1
+ <template>
2
+ <!-- An A4 sheet on the reader backdrop with a few pulsing text lines, matching the
3
+ real PdfPreview framing (centered, capped ~800px, gray backdrop). -->
4
+ <div
5
+ class="overflow-hidden bg-gray-100"
6
+ :class="fill ? 'min-h-0 flex-1' : 'max-h-[80vh]'"
7
+ role="status"
8
+ :aria-label="t('Chargement de l\'aperçu…')"
9
+ >
10
+ <div class="flex justify-center p-4">
11
+ <div class="aspect-[210/297] w-[800px] max-w-full bg-white p-10 shadow-sm">
12
+ <div class="animate-pulse-placeholder space-y-6">
13
+ <div class="space-y-3">
14
+ <div class="h-5 w-1/2 rounded bg-gray-200" />
15
+ <div class="h-3 w-full rounded bg-gray-200" />
16
+ <div class="h-3 w-full rounded bg-gray-200" />
17
+ <div class="h-3 w-5/6 rounded bg-gray-200" />
18
+ </div>
19
+ <div class="space-y-3">
20
+ <div class="h-3 w-full rounded bg-gray-200" />
21
+ <div class="h-3 w-11/12 rounded bg-gray-200" />
22
+ <div class="h-3 w-full rounded bg-gray-200" />
23
+ <div class="h-3 w-3/4 rounded bg-gray-200" />
24
+ </div>
25
+ <div class="space-y-3">
26
+ <div class="h-3 w-full rounded bg-gray-200" />
27
+ <div class="h-3 w-5/6 rounded bg-gray-200" />
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { useTranslation } from '../../composables/useTranslation'
37
+
38
+ defineProps<{
39
+ // Fill the available height (fullscreen) instead of the default capped height.
40
+ fill?: boolean
41
+ }>()
42
+
43
+ const { t } = useTranslation()
44
+ </script>
@@ -1,15 +1,15 @@
1
1
  <template>
2
- <div>
2
+ <div class="flex h-full flex-col">
3
3
  <PreviewUnavailable v-if="hasError">
4
4
  {{ t("L'aperçu cartographique de ce fichier n'a pas pu être chargé.") }}
5
5
  </PreviewUnavailable>
6
6
  <div
7
7
  v-else
8
- class="-mx-4"
8
+ class="flex min-h-0 flex-1 flex-col"
9
9
  >
10
10
  <div
11
11
  v-if="pmtilesViewerUrl"
12
- class="bg-blue-100 text-datagouv fr-hidden fr-unhidden-md p-4"
12
+ class="bg-blue-100 text-datagouv fr-hidden fr-unhidden-md p-4 shrink-0"
13
13
  >
14
14
  <div class="fr-grid-row fr-grid-row--middle fr-grid-row--gutters">
15
15
  <div
@@ -35,12 +35,17 @@
35
35
  </p>
36
36
  </div>
37
37
  </div>
38
- <div
39
- ref="containerRef"
40
- style="height: 600px;"
41
- />
42
- <div class="fr-px-5v fr-pt-5v">
43
- {{ t("Aperçu de la carte mis à jour le {date}", { date: lastUpdate }) }}
38
+ <div class="relative min-h-0 flex-1">
39
+ <div
40
+ ref="containerRef"
41
+ class="size-full"
42
+ />
43
+ <!-- Freshness caption overlaid inside the map (bottom-left, the corner left
44
+ free by the navigation/geolocate controls top-right and the attribution
45
+ bottom-right) so the map itself can use the whole height. -->
46
+ <div class="pointer-events-none absolute bottom-2 left-2 z-10 rounded bg-white/90 px-2 py-1 text-[12px] leading-4 text-gray-medium shadow-sm">
47
+ {{ t("Aperçu de la carte mis à jour le {date}", { date: lastUpdate }) }}
48
+ </div>
44
49
  </div>
45
50
  </div>
46
51
  </div>
@@ -62,7 +67,7 @@ import styleVector from '../../../assets/json/vector.json'
62
67
  import PreviewUnavailable from './PreviewUnavailable.vue'
63
68
  import { useTranslation } from '../../composables/useTranslation'
64
69
  import franceSvg from './france.svg?raw'
65
- import { getOwnerName, getOwnerPage } from '../../functions/owned'
70
+ import { getMapAttribution } from '../../functions/owned'
66
71
 
67
72
  const props = defineProps<{ resource: Resource, dataset: Dataset | DatasetV2 }>()
68
73
 
@@ -89,11 +94,7 @@ const lastUpdate = computed(() => {
89
94
 
90
95
  const container = useTemplateRef('containerRef')
91
96
 
92
- const attributions = computed(() => {
93
- if (!props.dataset.organization && !props.dataset.owner)
94
- return ''
95
- return `© <a href="${getOwnerPage(props.dataset)}" target="_blank">${getOwnerName(props.dataset)}</a>`
96
- })
97
+ const attributions = computed(() => getMapAttribution(props.dataset))
97
98
 
98
99
  async function displayMap() {
99
100
  await import('maplibre-gl/dist/maplibre-gl.css')
@@ -104,12 +105,22 @@ async function displayMap() {
104
105
  const p = new PMTiles(pmtilesUrl.value)
105
106
  protocol.add(p)
106
107
 
107
- p.getHeader().then((h: { maxZoom: number, centerLon: number, centerLat: number }) => {
108
+ p.getHeader().then((h: { minLon: number, minLat: number, maxLon: number, maxLat: number, centerZoom: number, centerLon: number, centerLat: number }) => {
109
+ // Frame the data's bounding box (carried by the archive header) so we land on
110
+ // the data. Fall back to the header center when the bbox is degenerate — an
111
+ // archive with a single feature, or bounds left unset (all zeroes).
112
+ const hasBounds = h.minLon < h.maxLon && h.minLat < h.maxLat
108
113
  const map = new maplibregl.Map({
109
114
  container: container.value!,
110
- style: styleVector as maplibregl.StyleSpecification,
111
- zoom: h.maxZoom - 2,
112
- center: [h.centerLon, h.centerLat],
115
+ // JSON import is inferred too loosely (e.g. center as number[]) to match
116
+ // maplibre's strict StyleSpecification, so cast through unknown.
117
+ style: styleVector as unknown as maplibregl.StyleSpecification,
118
+ ...(hasBounds
119
+ ? {
120
+ bounds: [[h.minLon, h.minLat], [h.maxLon, h.maxLat]] as [[number, number], [number, number]],
121
+ fitBoundsOptions: { padding: 24 },
122
+ }
123
+ : { center: [h.centerLon, h.centerLat] as [number, number], zoom: h.centerZoom }),
113
124
  })
114
125
  map.addControl(new maplibregl.NavigationControl())
115
126
  map.addControl(
@@ -90,7 +90,11 @@
90
90
  @change="changePage"
91
91
  />
92
92
  <div class="fr-px-5v">
93
- {{ t("Dernière mise à jour de la prévisualisation : {date}", { date: lastUpdate }) }} —
93
+ <TranslationT keypath="Dernière mise à jour de la prévisualisation : {date}">
94
+ <template #date>
95
+ <FormattedDate :date="parsingFinishedAt" />
96
+ </template>
97
+ </TranslationT> —
94
98
  {{ t('{count} colonnes', columns.length) }} —
95
99
  {{ t('Lignes {count}', rowCount) }}
96
100
  </div>
@@ -103,7 +107,8 @@ import { computed, onMounted, ref } from 'vue'
103
107
  import { RiArrowDownLine, RiArrowUpLine, RiExternalLinkFill } from '@remixicon/vue'
104
108
  import Pagination from '../Pagination.vue'
105
109
  import { getData, type SortConfig } from '../../functions/tabularApi'
106
- import { useFormatDate } from '../../functions/dates'
110
+ import FormattedDate from '../FormattedDate.vue'
111
+ import TranslationT from '../TranslationT.vue'
107
112
  import { trackEvent } from '../../functions/matomo'
108
113
  import type { Resource } from '../../types/resources'
109
114
  import { useComponentsConfig } from '../../config'
@@ -116,7 +121,6 @@ import PreviewLoader from './PreviewLoader.vue'
116
121
  const props = defineProps<{ resource: Resource }>()
117
122
 
118
123
  const { t } = useTranslation()
119
- const { formatDate } = useFormatDate()
120
124
 
121
125
  const rows = ref<Array<Record<string, unknown>>>([])
122
126
  const columns = ref<Array<string>>([])
@@ -196,7 +200,7 @@ function sortByField(col: string) {
196
200
  getTableInfos(currentPage.value, sortConfig.value)
197
201
  };
198
202
 
199
- const lastUpdate = computed(() => formatDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined))
203
+ const parsingFinishedAt = computed(() => props.resource.extras['analysis:parsing:finished_at'] as string | undefined)
200
204
 
201
205
  onMounted(() => {
202
206
  getTableInfos(currentPage.value)
@@ -1,29 +1,34 @@
1
1
  <template>
2
- <div class="text-xs">
2
+ <div
3
+ class="text-xs"
4
+ :class="fill ? 'flex min-h-0 flex-1 flex-col' : ''"
5
+ >
3
6
  <slot
4
7
  v-if="data !== null"
5
8
  :data="data"
6
9
  />
7
- <div
10
+ <slot
8
11
  v-else-if="loading"
9
- class="text-gray-medium"
12
+ name="loading"
10
13
  >
11
- {{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
12
- </div>
14
+ <div class="text-gray-medium">
15
+ {{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
16
+ </div>
17
+ </slot>
13
18
  <PreviewUnavailable v-else-if="!isSizeAllowed">
14
19
  {{ fileSizeBytes
15
- ? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.", { fileType })
16
- : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
20
+ ? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le pour le consulter.", { fileType })
21
+ : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le pour le consulter.")
17
22
  }}
18
23
  </PreviewUnavailable>
19
24
  <PreviewUnavailable v-else-if="corsStatus === 'blocked'">
20
- {{ t("Ce fichier {fileType} ne peut pas être prévisualisé car il est hébergé sur un site distant qui restreint l'accès (CORS). Téléchargez-le depuis l'onglet Téléchargements.", { fileType }) }}
25
+ {{ t("Ce fichier {fileType} ne peut pas être prévisualisé car il est hébergé sur un site distant qui restreint l'accès (CORS). Téléchargez-le pour le consulter.", { fileType }) }}
21
26
  </PreviewUnavailable>
22
27
  <PreviewUnavailable v-else-if="error === 'network'">
23
- {{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le depuis l'onglet Téléchargements.") }}
28
+ {{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le pour le consulter.") }}
24
29
  </PreviewUnavailable>
25
30
  <PreviewUnavailable v-else-if="error">
26
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
31
+ {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
27
32
  </PreviewUnavailable>
28
33
  </div>
29
34
  </template>
@@ -40,6 +45,8 @@ const props = defineProps<{
40
45
  resource: Resource
41
46
  maxSize: number | undefined
42
47
  load: () => Promise<unknown>
48
+ // Fill the available height (fullscreen) instead of sizing to content.
49
+ fill?: boolean
43
50
  }>()
44
51
 
45
52
  const emit = defineEmits<{
@@ -47,7 +47,7 @@
47
47
  <CopyButton
48
48
  :label="t('Copier le lien')"
49
49
  :copied-label="t('Lien copié !')"
50
- :text="resourceExternalUrl"
50
+ :text="externalUrl"
51
51
  class="z-2"
52
52
  />
53
53
  </div>
@@ -60,7 +60,17 @@
60
60
  aria-hidden="true"
61
61
  class="size-3 fill-gray-medium"
62
62
  />
63
- <span class="text-xs mb-0">{{ t('Mis à jour {date}', { date: formatRelativeIfRecentDate(lastUpdate) }) }}</span>
63
+ <TranslationT
64
+ class="text-xs mb-0"
65
+ keypath="Mis à jour {date}"
66
+ >
67
+ <template #date>
68
+ <FormattedDate
69
+ :date="lastUpdate"
70
+ format="relative"
71
+ />
72
+ </template>
73
+ </TranslationT>
64
74
  <RiSubtractLine
65
75
  aria-hidden="true"
66
76
  class="size-3 fill-gray-medium"
@@ -197,7 +207,10 @@
197
207
  :key="tab.key"
198
208
  class="px-4"
199
209
  >
200
- <div v-if="tab.key === 'map'">
210
+ <div
211
+ v-if="tab.key === 'map'"
212
+ class="-mx-4 h-[600px]"
213
+ >
201
214
  <Pmtiles
202
215
  v-if="hasPmtiles"
203
216
  :resource="resource"
@@ -224,6 +237,10 @@
224
237
  v-else-if="resource.format && resource.format.toLowerCase() === 'xml'"
225
238
  :resource="resource"
226
239
  />
240
+ <ImagePreview
241
+ v-else-if="isImagePreviewFormat(resource.format)"
242
+ :resource="resource"
243
+ />
227
244
  <!-- Show Datafair embedded preview (koumoul) -->
228
245
  <DatafairPreview
229
246
  v-else-if="hasDatafairPreview"
@@ -271,7 +288,7 @@
271
288
  />
272
289
  </div>
273
290
  <div
274
- v-if="tab.key === 'swagger'"
291
+ v-if="tab.key === 'api'"
275
292
  >
276
293
  <div class="fr-mb-4w">
277
294
  <p>{{ t("Cette API est générée automatiquement par {platform} à partir du fichier.", { platform: config.name }) }}</p>
@@ -296,7 +313,8 @@ import { ref, computed, defineAsyncComponent } from 'vue'
296
313
  import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiSubtractLine } from '@remixicon/vue'
297
314
  import OrganizationNameWithCertificate from '../OrganizationNameWithCertificate.vue'
298
315
  import { filesize, summarize } from '../../functions/helpers'
299
- import { useFormatDate } from '../../functions/dates'
316
+ import FormattedDate from '../FormattedDate.vue'
317
+ import TranslationT from '../TranslationT.vue'
300
318
  import MarkdownViewer from '../MarkdownViewer.vue'
301
319
  import type { CommunityResource, Resource } from '../../types/resources'
302
320
  import type { Dataset, DatasetV2 } from '../../types/datasets'
@@ -309,7 +327,7 @@ import { trackEvent } from '../../functions/matomo'
309
327
  import CopyButton from '../CopyButton.vue'
310
328
  import { useComponentsConfig } from '../../config'
311
329
  import { getOwnerName } from '../../functions/owned'
312
- import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize } from '../../functions/resources'
330
+ import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceFilesize, isImagePreviewFormat, resolveResourceExternalUrl } from '../../functions/resources'
313
331
  import BrandedButton from '../BrandedButton.vue'
314
332
  import { useTranslation } from '../../composables/useTranslation'
315
333
  import { useHasTabularData } from '../../composables/useHasTabularData'
@@ -331,6 +349,8 @@ const props = withDefaults(defineProps<{
331
349
  isCommunityResource?: boolean
332
350
  resource: Resource | CommunityResource
333
351
  canEdit?: boolean
352
+ // Overrides the "Copier le lien" target.
353
+ resourceExternalUrl?: (resource: Resource | CommunityResource) => string
334
354
  }>(), {
335
355
  expandedOnMount: false,
336
356
  isCommunityResource: false,
@@ -344,18 +364,18 @@ const Pmtiles = defineAsyncComponent(() => import('./Pmtiles.client.vue'))
344
364
  const JsonPreview = defineAsyncComponent(() => import('./JsonPreview.client.vue'))
345
365
  const PdfPreview = defineAsyncComponent(() => import('./PdfPreview.client.vue'))
346
366
  const XmlPreview = defineAsyncComponent(() => import('./XmlPreview.client.vue'))
367
+ const ImagePreview = defineAsyncComponent(() => import('./ImagePreview.client.vue'))
347
368
  const DatafairPreview = defineAsyncComponent(() => import('./Datafair.client.vue'))
348
369
 
349
370
  const { t } = useTranslation()
350
- const { formatRelativeIfRecentDate } = useFormatDate()
351
371
  const checkTabularData = useHasTabularData()
352
372
 
353
373
  const hasPreview = computed(() => {
354
- // For JSON, PDF, and XML files, show preview.
374
+ // For JSON, PDF, XML, and image files, show preview.
355
375
  // We cannot check for CORS issues here because we cannot use an async component here.
356
376
  // If there is a CORS issue when fetching the file for preview, it will be managed and displayed as an error banner by the preview component.
357
377
  const format = props.resource.format?.toLowerCase()
358
- return format === 'json' || format === 'pdf' || format === 'xml'
378
+ return format === 'json' || format === 'pdf' || format === 'xml' || isImagePreviewFormat(format)
359
379
  })
360
380
 
361
381
  const hasTabularData = computed(() => checkTabularData(props.resource))
@@ -417,7 +437,7 @@ const tabsOptions = computed(() => {
417
437
  options.push({ key: 'downloads', label: t('Téléchargements') })
418
438
 
419
439
  if (hasTabularData.value) {
420
- options.push({ key: 'swagger', label: t('Swagger') })
440
+ options.push({ key: 'api', label: t('API') })
421
441
  }
422
442
 
423
443
  return options
@@ -450,7 +470,7 @@ const resourceFilesize = computed(() => getResourceFilesize(props.resource))
450
470
  const unavailable = availabilityChecked && props.resource.extras['check:available'] === false
451
471
  const downloadButtonTitle = unavailable ? t(`Le robot de {certifier} n'a pas pu accéder à ce fichier - Télécharger le fichier en {format}`, { certifier: config.name, format: format.value }) : t(`Télécharger le fichier en {format}`, { format: format.value })
452
472
 
453
- const resourceExternalUrl = computed(() => getResourceExternalUrl(props.dataset, props.resource))
473
+ const externalUrl = computed(() => resolveResourceExternalUrl(props.dataset, props.resource, props.resourceExternalUrl))
454
474
 
455
475
  const resourceContentId = 'resource-' + props.resource.id
456
476
  const resourceHeaderId = 'resource-' + props.resource.id + '-header'