@datagouv/components-next 1.0.2-dev.15 → 1.0.2-dev.151

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 (115) hide show
  1. package/README.md +7 -6
  2. package/assets/main.css +4 -0
  3. package/dist/{Control-DuZJdKV_.js → Control-B9WQxlpJ.js} +1 -1
  4. package/dist/{Datafair.client-BtQqFBTQ.js → Datafair.client-MNAP27A-.js} +4 -4
  5. package/dist/{Event--kp8kMdJ.js → Event-iXDrp-fy.js} +31 -31
  6. package/dist/{Image-34hvypZI.js → Image-mNMBOofs.js} +9 -9
  7. package/dist/JsonPreview.client-C3dG7a_r.js +40 -0
  8. package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
  9. package/dist/{MapContainer.client-DB6p6KF8.js → MapContainer.client-B5YiX3NO.js} +38 -39
  10. package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
  11. package/dist/{PdfPreview.client-Dr4VcIur.js → PdfPreview.client-CERt2f57.js} +823 -855
  12. package/dist/Pmtiles.client-D8ug3toD.js +26142 -0
  13. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang--KItAayB.js +61 -0
  14. package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-VGQ7nkMS.js} +2 -2
  15. package/dist/{Tile-DbNFNPfU.js → Tile-CSpn0Ca8.js} +160 -163
  16. package/dist/{TileImage-BsXBxMtq.js → TileImage-CpbaEFwd.js} +143 -131
  17. package/dist/{View-BR92hTWP.js → View-BYBpt-AU.js} +483 -422
  18. package/dist/XmlPreview.client-1IAoOczZ.js +34 -0
  19. package/dist/{common-PJfpC179.js → common-BoeTUnDZ.js} +52 -52
  20. package/dist/components-next.css +1 -52
  21. package/dist/components-next.js +174 -142
  22. package/dist/components.css +2 -2
  23. package/dist/{index-D3rieddI.js → index-D86-g3i7.js} +21147 -15136
  24. package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-BjtNBpa8.js} +185 -187
  25. package/dist/main-BdrYl6mr.js +73598 -0
  26. package/dist/{proj-DsetBcW7.js → proj-DxGbobp7.js} +662 -626
  27. package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
  28. package/dist/{tilecoord-Db24Px13.js → tilecoord-DvX7WWiA.js} +273 -264
  29. package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
  30. package/dist/{vue3-xml-viewer.common-CJXShyPB.js → vue3-xml-viewer.common-BE7Fgsqp.js} +16 -16
  31. package/package.json +50 -36
  32. package/src/chart.ts +5 -0
  33. package/src/components/ActivityList/ActivityList.vue +3 -2
  34. package/src/components/Chart/ChartViewer.vue +233 -0
  35. package/src/components/Chart/ChartViewerWrapper.vue +182 -0
  36. package/src/components/DataserviceCard.vue +3 -0
  37. package/src/components/DatasetCard.vue +9 -4
  38. package/src/components/Form/Listbox.vue +101 -0
  39. package/src/components/Form/SearchableSelect.vue +2 -1
  40. package/src/components/InfiniteLoader.vue +53 -0
  41. package/src/components/ObjectCardHeader.vue +11 -4
  42. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  43. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  44. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  45. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  46. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  47. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  48. package/src/components/OpenApiViewer/openapi.ts +150 -0
  49. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  50. package/src/components/Pagination.vue +8 -5
  51. package/src/components/RadioInput.vue +7 -2
  52. package/src/components/ResourceAccordion/DataStructure.vue +11 -33
  53. package/src/components/ResourceAccordion/Downloads.vue +160 -0
  54. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
  55. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -3
  56. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  57. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -87
  58. package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -1
  59. package/src/components/ResourceAccordion/Preview.vue +11 -11
  60. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  61. package/src/components/ResourceAccordion/ResourceAccordion.vue +12 -111
  62. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
  63. package/src/components/ResourceExplorer/ResourceExplorer.vue +22 -19
  64. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  65. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +57 -148
  66. package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
  67. package/src/components/ReuseCard.vue +14 -6
  68. package/src/components/ReuseDetails.vue +2 -2
  69. package/src/components/ReuseHorizontalCard.vue +2 -2
  70. package/src/components/Search/GlobalSearch.vue +219 -117
  71. package/src/components/Search/SearchInput.vue +5 -4
  72. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  73. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  74. package/src/components/TabularExplorer/TabularExplorer.vue +1165 -0
  75. package/src/components/TabularExplorer/TabularFilterContent.vue +353 -0
  76. package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
  77. package/src/components/TabularExplorer/types.ts +86 -0
  78. package/src/composables/useHasTabularData.ts +15 -1
  79. package/src/composables/useMetrics.ts +1 -1
  80. package/src/composables/useResourceCapabilities.ts +22 -9
  81. package/src/composables/useSearchFilter.ts +120 -0
  82. package/src/composables/useStableQueryParams.ts +38 -6
  83. package/src/composables/useTabularProfile.ts +70 -0
  84. package/src/config.ts +20 -3
  85. package/src/functions/activities.ts +3 -3
  86. package/src/functions/api.ts +20 -42
  87. package/src/functions/api.types.ts +1 -0
  88. package/src/functions/charts.ts +79 -0
  89. package/src/functions/datasets.ts +0 -17
  90. package/src/functions/dates.ts +3 -2
  91. package/src/functions/metrics.ts +6 -4
  92. package/src/functions/resources.ts +110 -1
  93. package/src/functions/tabular.ts +171 -0
  94. package/src/functions/tabularApi.ts +96 -11
  95. package/src/main.ts +97 -10
  96. package/src/types/dataservices.ts +2 -0
  97. package/src/types/organizations.ts +10 -0
  98. package/src/types/pages.ts +0 -5
  99. package/src/types/posts.ts +2 -2
  100. package/src/types/reports.ts +5 -1
  101. package/src/types/resources.ts +0 -1
  102. package/src/types/reuses.ts +12 -1
  103. package/src/types/search.ts +63 -1
  104. package/src/types/site.ts +5 -3
  105. package/src/types/ui.ts +2 -0
  106. package/src/types/users.ts +2 -1
  107. package/src/types/visualizations.ts +93 -0
  108. package/assets/swagger-themes/newspaper.css +0 -1670
  109. package/dist/JsonPreview.client-DojrmGyU.js +0 -72
  110. package/dist/Pmtiles.client-DbMjfK00.js +0 -25185
  111. package/dist/Swagger.client-Db_xYKJv.js +0 -4
  112. package/dist/XmlPreview.client-CHxSFhd7.js +0 -64
  113. package/dist/main-D70Xt4a7.js +0 -105854
  114. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  115. package/src/functions/pagination.ts +0 -9
@@ -7,6 +7,8 @@
7
7
  :key="selectedResource.id"
8
8
  :dataset
9
9
  :resource="selectedResource"
10
+ :resources="flatResources"
11
+ @select="selectResource"
10
12
  />
11
13
  <div
12
14
  v-else-if="search"
@@ -30,16 +32,18 @@
30
32
  </BrandedButton>
31
33
  </div>
32
34
  </div>
33
- <ResourceExplorerSidebar
34
- :resources="allResources"
35
- :selected-resource-id="selectedResource?.id ?? null"
36
- :collapsed="sidebarCollapsed"
37
- :search
38
- @select="selectResource"
39
- @load-more="loadMore"
40
- @update:collapsed="sidebarCollapsed = $event"
41
- @update:search="updateSearch($event)"
42
- />
35
+ <div class="hidden md:block">
36
+ <ResourceExplorerSidebar
37
+ :resources="allResources"
38
+ :selected-resource-id="selectedResource?.id ?? null"
39
+ :collapsed="sidebarCollapsed"
40
+ :search
41
+ @select="selectResource"
42
+ @load-more="loadMore"
43
+ @update:collapsed="sidebarCollapsed = $event"
44
+ @update:search="updateSearch($event)"
45
+ />
46
+ </div>
43
47
  </div>
44
48
  </div>
45
49
  <div
@@ -67,7 +71,7 @@ import { useComponentsConfig } from '../../config'
67
71
  import { useTranslation } from '../../composables/useTranslation'
68
72
  import { useDebouncedRef } from '../../composables/useDebouncedRef'
69
73
  import { useFetch } from '../../functions/api'
70
- import { RESOURCE_TYPE } from '../../functions/resources'
74
+ import { RESOURCE_EXPLORER_PAGE_SIZE, RESOURCE_TYPE } from '../../functions/resources'
71
75
  import type { PaginatedArray } from '../../types/api'
72
76
  import type { DatasetV2 } from '../../types/datasets'
73
77
  import type { Resource, ResourceGroup, ResourceType } from '../../types/resources'
@@ -90,7 +94,6 @@ const sidebarCollapsed = ref(false)
90
94
  const search = ref('')
91
95
  const { debounced: searchDebounced, flush } = useDebouncedRef(search, config.searchDebounce ?? 300)
92
96
 
93
- const PAGE_SIZE = 10
94
97
  const url = computed(() => `/api/2/datasets/${props.dataset.id}/resources/`)
95
98
 
96
99
  // Resource ID from URL query
@@ -99,32 +102,32 @@ const resourceIdQuery = useRouteQuery<string | undefined>('resource_id')
99
102
  // Fetch resources for each type
100
103
  const mainParams = computed(() => ({
101
104
  type: 'main' as const,
102
- page_size: PAGE_SIZE,
105
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
103
106
  q: searchDebounced.value || undefined,
104
107
  }))
105
108
  const documentationParams = computed(() => ({
106
109
  type: 'documentation' as const,
107
- page_size: PAGE_SIZE,
110
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
108
111
  q: searchDebounced.value || undefined,
109
112
  }))
110
113
  const updateParams = computed(() => ({
111
114
  type: 'update' as const,
112
- page_size: PAGE_SIZE,
115
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
113
116
  q: searchDebounced.value || undefined,
114
117
  }))
115
118
  const apiParams = computed(() => ({
116
119
  type: 'api' as const,
117
- page_size: PAGE_SIZE,
120
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
118
121
  q: searchDebounced.value || undefined,
119
122
  }))
120
123
  const codeParams = computed(() => ({
121
124
  type: 'code' as const,
122
- page_size: PAGE_SIZE,
125
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
123
126
  q: searchDebounced.value || undefined,
124
127
  }))
125
128
  const otherParams = computed(() => ({
126
129
  type: 'other' as const,
127
- page_size: PAGE_SIZE,
130
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
128
131
  q: searchDebounced.value || undefined,
129
132
  }))
130
133
 
@@ -165,7 +168,7 @@ const loadMoreType = ref<ResourceType>('main')
165
168
  const loadMorePage = ref(1)
166
169
  const loadMoreParams = computed(() => ({
167
170
  type: loadMoreType.value,
168
- page_size: PAGE_SIZE,
171
+ page_size: RESOURCE_EXPLORER_PAGE_SIZE,
169
172
  page: loadMorePage.value,
170
173
  q: searchDebounced.value || undefined,
171
174
  }))
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <aside
3
3
  v-if="!collapsed"
4
- class="w-72 shrink-0 pl-4"
4
+ class="w-full md:w-72 shrink-0 p-4 md:pr-0"
5
5
  >
6
6
  <div class="flex items-center justify-between mb-3">
7
7
  <h3 class="text-sm font-bold uppercase mb-0">
@@ -32,7 +32,7 @@
32
32
  >
33
33
  </div>
34
34
 
35
- <div class="space-y-4 overflow-y-auto">
35
+ <div class="space-y-4 overflow-y-auto md:max-h-[calc(100vh-14rem)]">
36
36
  <div
37
37
  v-for="group in resources"
38
38
  :key="group.type"
@@ -2,24 +2,31 @@
2
2
  <div class="border border-gray-default">
3
3
  <header class="p-4 flex flex-wrap md:flex-nowrap gap-4 items-center justify-between">
4
4
  <div>
5
- <div class="flex items-center mb-1">
5
+ <div class="flex items-center gap-1 mb-1">
6
6
  <h3 class="m-0 flex items-baseline text-base font-bold leading-tight">
7
7
  <ResourceIcon
8
8
  :resource
9
- class="size-3.5 mr-1"
9
+ class="size-3.5 mr-1 shrink-0 translate-y-px"
10
10
  />
11
11
  <span class="line-clamp-2">{{ resource.title || t('Fichier sans nom') }}</span>
12
12
  </h3>
13
+ <ResourceSelector
14
+ v-if="resources && resources.length > 1"
15
+ :resources
16
+ :selected-id="resource.id"
17
+ @select="emit('select', $event)"
18
+ />
13
19
  <CopyButton
14
20
  :label="t('Copier le lien')"
15
21
  :copied-label="t('Lien copié !')"
16
22
  :text="resourceExternalUrl"
23
+ class="hidden md:inline-flex"
17
24
  />
18
25
  </div>
19
- <div class="text-gray-medium text-xs flex items-center gap-1">
26
+ <div class="text-gray-medium text-xs flex items-center gap-1 flex-wrap">
20
27
  <SchemaBadge :resource />
21
28
  <RiSubtractLine
22
- v-if="resource.schema"
29
+ v-if="resource.schema?.name || resource.schema?.url"
23
30
  aria-hidden="true"
24
31
  class="size-3 fill-gray-medium"
25
32
  />
@@ -115,6 +122,11 @@
115
122
  v-if="ogcWms"
116
123
  :resource="resource"
117
124
  />
125
+ <PreviewUnavailable v-if="!hasPmtiles && !ogcWms && hasPmtilesError">
126
+ {{ t("La carte n'a pas pu être générée automatiquement pour ce fichier.") }}
127
+ <br>
128
+ <span class="text-gray-medium text-xs">{{ pmtilesError }}</span>
129
+ </PreviewUnavailable>
118
130
  </div>
119
131
  <div v-if="tab.key === 'data'">
120
132
  <JsonPreview
@@ -134,16 +146,27 @@
134
146
  :resource="resource"
135
147
  :dataset="dataset"
136
148
  />
137
- <SwaggerClient
149
+ <OpenApiViewer
138
150
  v-else-if="hasOpenAPIPreview"
139
151
  :url="resource.extras['apidocUrl'] as string"
140
152
  />
141
- <Preview
153
+ <TabularExplorer
142
154
  v-else-if="hasTabularData"
143
- :resource="resource"
155
+ :resource-id="resource.id"
144
156
  />
145
157
  <PreviewUnavailable v-else>
146
- {{ t("Ce fichier ne peut pas être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.") }}
158
+ <!-- "File too large to download" is the only analysis:error value from hydra for now -->
159
+ <template v-if="resource.extras['analysis:error'] === 'File too large to download'">
160
+ {{ t("Ce fichier est trop volumineux pour être analysé et prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.") }}
161
+ </template>
162
+ <template v-else-if="resource.extras['analysis:parsing:error']">
163
+ {{ t("L'analyse de ce fichier a rencontré une erreur, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.") }}
164
+ <br>
165
+ <span class="text-gray-medium text-xs">{{ resource.extras['analysis:parsing:error'] }}</span>
166
+ </template>
167
+ <template v-else>
168
+ {{ t("Ce fichier ne peut pas être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.") }}
169
+ </template>
147
170
  </PreviewUnavailable>
148
171
  </div>
149
172
  <div v-if="tab.key === 'description'">
@@ -162,137 +185,22 @@
162
185
  <Metadata :resource />
163
186
  </div>
164
187
  <div v-if="tab.key === 'downloads'">
165
- <dl class="fr-pl-0">
166
- <dt
167
- v-if="resource.format === 'url'"
168
- class="font-bold fr-text--sm fr-mb-0"
169
- >
170
- {{ t("URL d'origine") }}
171
- </dt>
172
- <dt
173
- v-else
174
- class="font-bold fr-text--sm fr-mb-0"
175
- >
176
- {{ t('Format original') }}
177
- </dt>
178
- <dd class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center">
179
- <span
180
- v-if="resource.format === 'url'"
181
- class="inline-flex items-center max-w-full"
182
- >
183
- <a
184
- :href="resource.latest"
185
- class="fr-link no-icon-after truncate"
186
- rel="ugc nofollow noopener"
187
- target="_blank"
188
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
189
- >
190
- {{ resource.url }}
191
- </a>
192
- <span class="fr-ml-1v fr-icon-external-link-line fr-icon--sm shrink-0" />
193
- </span>
194
- <span v-else>
195
- <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
196
- <a
197
- :href="resource.latest"
198
- class="fr-link"
199
- rel="ugc nofollow noopener"
200
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${resource.format}`)"
201
- >
202
- <span>{{ t('Format {format}', { format: resource.format }) }}<span v-if="resourceFilesize"> - {{ filesize(resourceFilesize) }}</span></span>
203
- </a>
204
- </span>
205
- <CopyButton
206
- :label="t('Copier le lien')"
207
- :copied-label="t('Lien copié !')"
208
- :text="resource.latest"
209
- class="relative"
210
- />
211
- </dd>
212
- <template v-if="generatedFormats.length">
213
- <dt class="font-bold fr-text--sm fr-mb-0">
214
- {{ t('Formats générés automatiquement par {platform} (dernière mise à jour {date})', { platform: config.name, date: conversionsLastUpdate }) }}
215
- </dt>
216
- <dd
217
- v-for="generatedFormat in generatedFormats"
218
- :key="generatedFormat.format"
219
- class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
220
- >
221
- <span>
222
- <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
223
- <a
224
- :href="generatedFormat.url"
225
- class="fr-link"
226
- rel="ugc nofollow noopener"
227
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${generatedFormat.format}`)"
228
- >
229
- <span>{{ t('Format {format}', { format: generatedFormat.format }) }}<span v-if="generatedFormat.size"> - {{ filesize(generatedFormat.size) }}</span></span>
230
- </a>
231
- </span>
232
- <CopyButton
233
- :label="t('Copier le lien')"
234
- :copied-label="t('Lien copié !')"
235
- :text="generatedFormat.url"
236
- class="relative"
237
- />
238
- </dd>
239
- </template>
240
- <template v-if="wfsFormats.length">
241
- <dt class="font-bold fr-text--sm fr-mb-0">
242
- <div class="flex gap-1 items-center">
243
- {{ t('Formats exportés depuis le service WFS') }}
244
- <span v-if="defaultWfsProjection"> ({{ t('projection {crs}', { crs: defaultWfsProjection }) }})</span>
245
- <Tooltip>
246
- <RiInformationLine
247
- class="flex-none size-4"
248
- :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é.`)"
249
- aria-hidden="true"
250
- />
251
- <template #tooltip>
252
- <p class="text-sm font-normal mb-0">
253
- {{ t(`Le lien de téléchargement interroge directement le flux WFS distant.`) }}
254
- </p>
255
- <p class="text-sm font-normal mb-0">
256
- {{ t(`Le nombre de features téléchargées peut être limité.`) }}
257
- </p>
258
- </template>
259
- </Tooltip>
260
- </div>
261
- </dt>
262
- <dd
263
- v-for="wfsFormat in wfsFormats"
264
- :key="wfsFormat.format"
265
- class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
266
- >
267
- <span>
268
- <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
269
- <a
270
- :href="wfsFormat.url"
271
- class="fr-link"
272
- rel="ugc nofollow noopener"
273
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${wfsFormat.format}`)"
274
- >
275
- <span>{{ t('Format {format}', { format: wfsFormat.format }) }}</span>
276
- </a>
277
- </span>
278
- <CopyButton
279
- :label="t('Copier le lien')"
280
- :copied-label="t('Lien copié !')"
281
- :text="wfsFormat.url"
282
- class="relative"
283
- />
284
- </dd>
285
- </template>
286
- </dl>
188
+ <Downloads
189
+ :resource="resource"
190
+ :dataset="dataset"
191
+ />
287
192
  </div>
288
- <div v-if="tab.key === 'swagger'">
193
+ <div v-if="tab.key === 'api'">
289
194
  <div class="fr-mb-4w">
290
195
  <p>{{ t("Cette API est générée automatiquement par {platform} à partir du fichier.", { platform: config.name }) }}</p>
291
196
  <p>{{ t("- Si le fichier est modifié, l'API sera mise à jour et sa structure pourra changer.") }}</p>
292
197
  <p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
293
198
  <p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
199
+ <p v-if="config.tabularApiUrl">
200
+ {{ t("L'URL de base de l'API est {url}", { url: config.tabularApiUrl }) }}
201
+ </p>
294
202
  </div>
295
- <Swagger
203
+ <OpenApiViewer
296
204
  v-if="hasTabularData"
297
205
  :url="`${config.tabularApiUrl}/api/resources/${resource.id}/swagger/`"
298
206
  />
@@ -306,32 +214,33 @@
306
214
 
307
215
  <script setup lang="ts">
308
216
  import { computed, defineAsyncComponent } from 'vue'
309
- import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiInformationLine, RiSubtractLine } from '@remixicon/vue'
217
+ import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiSubtractLine } from '@remixicon/vue'
310
218
  import PreviewUnavailable from '../ResourceAccordion/PreviewUnavailable.vue'
311
219
  import { toast } from 'vue-sonner'
312
220
  import BrandedButton from '../BrandedButton.vue'
313
221
  import CopyButton from '../CopyButton.vue'
314
222
  import MarkdownViewer from '../MarkdownViewer.vue'
315
223
  import ResourceIcon from '../ResourceAccordion/ResourceIcon.vue'
316
- import Swagger from '../ResourceAccordion/Swagger.client.vue'
224
+ import OpenApiViewer from '../OpenApiViewer/OpenApiViewer.vue'
317
225
  import TabGroup from '../Tabs/TabGroup.vue'
318
226
  import TabList from '../Tabs/TabList.vue'
319
227
  import Tab from '../Tabs/Tab.vue'
320
228
  import TabPanels from '../Tabs/TabPanels.vue'
321
229
  import TabPanel from '../Tabs/TabPanel.vue'
322
- import Tooltip from '../Tooltip.vue'
323
- import Preview from '../ResourceAccordion/Preview.vue'
230
+ import TabularExplorer from '../TabularExplorer/TabularExplorer.vue'
324
231
  import DataStructure from '../ResourceAccordion/DataStructure.vue'
232
+ import Downloads from '../ResourceAccordion/Downloads.vue'
325
233
  import Metadata from '../ResourceAccordion/Metadata.vue'
326
234
  import SchemaBadge from '../ResourceAccordion/SchemaBadge.vue'
235
+ import ResourceSelector from './ResourceSelector.vue'
327
236
  import { filesize, summarize } from '../../functions/helpers'
328
- import { getResourceFormatIcon } from '../../functions/resources'
329
- import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
237
+ import { getResourceFormatIcon, getResourceExternalUrl, getResourceFilesize } from '../../functions/resources'
330
238
  import { trackEvent } from '../../functions/matomo'
331
239
  import { useComponentsConfig } from '../../config'
332
240
  import { useFormatDate } from '../../functions/dates'
333
241
  import { useTranslation } from '../../composables/useTranslation'
334
242
  import { useResourceCapabilities } from '../../composables/useResourceCapabilities'
243
+ import { provideTabularProfile } from '../../composables/useTabularProfile'
335
244
  import type { Resource } from '../../types/resources'
336
245
  import type { Dataset, DatasetV2 } from '../../types/datasets'
337
246
 
@@ -353,13 +262,15 @@ const MapContainer = defineAsyncComponent(() =>
353
262
  const Pmtiles = defineAsyncComponent(() =>
354
263
  import('../ResourceAccordion/Pmtiles.client.vue'),
355
264
  )
356
- const SwaggerClient = defineAsyncComponent(() =>
357
- import('../ResourceAccordion/Swagger.client.vue'),
358
- )
359
265
 
360
266
  const props = defineProps<{
361
267
  dataset: Dataset | DatasetV2
362
268
  resource: Resource
269
+ resources?: Resource[]
270
+ }>()
271
+
272
+ const emit = defineEmits<{
273
+ select: [resource: Resource]
363
274
  }>()
364
275
 
365
276
  const { t } = useTranslation()
@@ -369,17 +280,19 @@ const { formatRelativeIfRecentDate } = useFormatDate()
369
280
  const {
370
281
  hasTabularData,
371
282
  hasPmtiles,
283
+ hasPmtilesError,
284
+ pmtilesError,
372
285
  hasDatafairPreview,
373
286
  hasOpenAPIPreview,
374
287
  ogcService,
375
288
  ogcWms,
376
- generatedFormats,
377
- wfsFormats,
378
- defaultWfsProjection,
379
289
  isResourceUrl,
380
290
  tabsOptions,
381
291
  } = useResourceCapabilities(() => props.resource, () => props.dataset)
382
292
 
293
+ // Share the tabular profile fetch between TabularExplorer and DataStructure tabs.
294
+ await provideTabularProfile(() => props.resource.id)
295
+
383
296
  const resourceFilesize = computed(() => getResourceFilesize(props.resource))
384
297
  const resourceExternalUrl = computed(() => getResourceExternalUrl(props.dataset, props.resource))
385
298
 
@@ -393,10 +306,6 @@ const downloadButtonTitle = computed(() => {
393
306
  return t('Télécharger le fichier en {format}', { format: format.value })
394
307
  })
395
308
 
396
- const conversionsLastUpdate = computed(() =>
397
- formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined),
398
- )
399
-
400
309
  const copyResourceUrl = async () => {
401
310
  try {
402
311
  await navigator.clipboard.writeText(props.resource.url)
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <Popover
3
+ v-slot="{ open, close }"
4
+ class="relative inline-block"
5
+ >
6
+ <slot
7
+ name="trigger"
8
+ :open="open"
9
+ >
10
+ <PopoverButton
11
+ class="inline-flex items-center justify-center size-6 rounded text-gray-plain hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-new-primary"
12
+ :aria-label="t('Choisir une autre ressource')"
13
+ >
14
+ <RiArrowDownSLine
15
+ class="size-4"
16
+ :class="{ 'rotate-180': open }"
17
+ aria-hidden="true"
18
+ />
19
+ </PopoverButton>
20
+ </slot>
21
+ <PopoverPanel
22
+ :class="searchable
23
+ ? 'absolute left-0 top-full z-50 mt-2 w-96 max-w-[calc(100vw-2rem)] bg-white border border-gray-default rounded shadow-lg p-3 space-y-2'
24
+ : 'absolute left-0 top-full z-50 mt-1 w-80 max-h-96 overflow-auto bg-white border border-gray-default rounded shadow-lg p-1'"
25
+ >
26
+ <input
27
+ v-if="searchable"
28
+ v-model="searchQuery"
29
+ type="search"
30
+ class="w-full border border-gray-default rounded px-2.5 py-1.5 text-sm"
31
+ :placeholder="t('Rechercher dans les ressources…')"
32
+ >
33
+ <ul
34
+ v-if="filteredResources.length > 0"
35
+ class="list-none p-0 m-0 space-y-0.5 max-h-80 overflow-y-auto"
36
+ >
37
+ <li
38
+ v-for="r in filteredResources"
39
+ :key="r.id"
40
+ >
41
+ <button
42
+ v-if="!isDisabled?.(r)"
43
+ type="button"
44
+ class="flex items-center gap-1.5 w-full text-left px-2 py-1.5 rounded text-sm hover:bg-gray-100 focus:outline-none focus-visible:bg-gray-100"
45
+ :class="{ 'font-bold bg-blue-50 text-new-primary': r.id === selectedId }"
46
+ @click="emit('select', r); close()"
47
+ >
48
+ <ResourceIcon
49
+ :resource="r"
50
+ class="size-3.5 shrink-0"
51
+ />
52
+ <span class="truncate">{{ r.title || t('Fichier sans nom') }}</span>
53
+ <span
54
+ v-if="r.format"
55
+ class="ml-auto text-xs text-gray-medium uppercase shrink-0"
56
+ >
57
+ {{ r.format }}
58
+ </span>
59
+ </button>
60
+ <div
61
+ v-else
62
+ class="flex items-center gap-1.5 px-2 py-1.5 rounded text-sm text-gray-medium cursor-not-allowed"
63
+ :title="disabledTitle"
64
+ >
65
+ <ResourceIcon
66
+ :resource="r"
67
+ class="size-3.5 shrink-0 opacity-50"
68
+ />
69
+ <span class="truncate opacity-70">{{ r.title || t('Fichier sans nom') }}</span>
70
+ </div>
71
+ </li>
72
+ </ul>
73
+ <p
74
+ v-else
75
+ class="text-sm text-gray-medium italic mb-0 px-2 py-2"
76
+ >
77
+ {{ t('Aucune ressource correspondante') }}
78
+ </p>
79
+ </PopoverPanel>
80
+ </Popover>
81
+ </template>
82
+
83
+ <script setup lang="ts">
84
+ import { computed, ref } from 'vue'
85
+ import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
86
+ import { RiArrowDownSLine } from '@remixicon/vue'
87
+ import { useTranslation } from '../../composables/useTranslation'
88
+ import ResourceIcon from '../ResourceAccordion/ResourceIcon.vue'
89
+ import type { Resource } from '../../types/resources'
90
+
91
+ const props = defineProps<{
92
+ resources: Resource[]
93
+ selectedId: string
94
+ searchable?: boolean
95
+ isDisabled?: (resource: Resource) => boolean
96
+ disabledTitle?: string
97
+ }>()
98
+
99
+ const emit = defineEmits<{
100
+ select: [resource: Resource]
101
+ }>()
102
+
103
+ const { t } = useTranslation()
104
+
105
+ const searchQuery = ref('')
106
+
107
+ const filteredResources = computed(() => {
108
+ if (!props.searchable) return props.resources
109
+ const q = searchQuery.value.trim().toLowerCase()
110
+ if (!q) return props.resources
111
+ return props.resources.filter(r => (r.title ?? '').toLowerCase().includes(q))
112
+ })
113
+ </script>
@@ -2,14 +2,17 @@
2
2
  <article class="fr-enlarge-link group/reuse-card bg-white border border-gray-default hover:bg-gray-some flex flex-col relative">
3
3
  <div class="flex flex-col h-full flex-1 order-2 px-8">
4
4
  <div class="order-1 flex flex-col px-4 py-1 h-full -mx-8">
5
- <h3 class="font-bold text-base mt-1 mb-0 truncate">
5
+ <component
6
+ :is="titleTag"
7
+ class="font-bold text-base mt-1 mb-0 truncate"
8
+ >
6
9
  <AppLink
7
10
  class="text-gray-title overflow-hidden"
8
11
  :to="reuseUrl"
9
12
  >
10
13
  {{ reuse.title }}
11
14
  </AppLink>
12
- </h3>
15
+ </component>
13
16
  <div class="order-3 text-sm m-0 text-gray-medium">
14
17
  <div class="text-sm mb-0 flex items-center">
15
18
  <ObjectCardOwner
@@ -65,15 +68,16 @@ import { RiLockLine, RiSubtractLine } from '@remixicon/vue'
65
68
  import { computed } from 'vue'
66
69
  import type { RouteLocationRaw } from 'vue-router'
67
70
  import { useFormatDate } from '../functions/dates'
68
- import type { Reuse } from '../types/reuses'
71
+ import type { Reuse, ReuseV2 } from '../types/reuses'
72
+ import type { TitleTag } from '../types/ui'
69
73
  import { useTranslation } from '../composables/useTranslation'
70
74
  import AppLink from './AppLink.vue'
71
75
  import ObjectCardOwner from './ObjectCardOwner.vue'
72
76
  import ReuseDetails from './ReuseDetails.vue'
73
77
  import Placeholder from './Placeholder.vue'
74
78
 
75
- const props = defineProps<{
76
- reuse: Reuse
79
+ const props = withDefaults(defineProps<{
80
+ reuse: Reuse | ReuseV2
77
81
 
78
82
  /**
79
83
  * The reuseUrl is a route location object to allow Vue Router to navigate to the details of a reuse.
@@ -86,7 +90,11 @@ const props = defineProps<{
86
90
  * It is used as a separate prop to allow other sites using the package to define their own organization pages.
87
91
  */
88
92
  organizationUrl?: RouteLocationRaw
89
- }>()
93
+
94
+ titleTag?: TitleTag
95
+ }>(), {
96
+ titleTag: 'h3',
97
+ })
90
98
 
91
99
  const { t } = useTranslation()
92
100
  const { formatRelativeIfRecentDate } = useFormatDate()
@@ -32,11 +32,11 @@
32
32
  import { RiEyeLine, RiStarLine, RiSubtractLine } from '@remixicon/vue'
33
33
  import { useReuseType } from '../composables/useReuseType'
34
34
  import { summarize } from '../functions/helpers'
35
- import type { Reuse } from '../types/reuses'
35
+ import type { Reuse, ReuseV2 } from '../types/reuses'
36
36
  import { useTranslation } from '../composables/useTranslation'
37
37
 
38
38
  const props = defineProps<{
39
- reuse: Reuse
39
+ reuse: Reuse | ReuseV2
40
40
  }>()
41
41
 
42
42
  const { t } = useTranslation()
@@ -60,7 +60,7 @@
60
60
  <script setup lang="ts">
61
61
  import { RiArchiveLine, RiLineChartLine, RiLockLine } from '@remixicon/vue'
62
62
  import type { RouteLocationRaw } from 'vue-router'
63
- import type { Reuse } from '../types/reuses'
63
+ import type { Reuse, ReuseV2 } from '../types/reuses'
64
64
  import { useTranslation } from '../composables/useTranslation'
65
65
  import ReuseDetails from './ReuseDetails.vue'
66
66
  import Placeholder from './Placeholder.vue'
@@ -71,7 +71,7 @@ import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
71
71
  import ObjectCardBadge from './ObjectCardBadge.vue'
72
72
 
73
73
  defineProps<{
74
- reuse: Reuse
74
+ reuse: Reuse | ReuseV2
75
75
  reuseUrl?: RouteLocationRaw
76
76
  organizationUrl?: RouteLocationRaw
77
77
  }>()