@datagouv/components-next 1.0.2-dev.9 → 1.0.2-dev.91

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 (96) hide show
  1. package/assets/main.css +4 -0
  2. package/dist/{Control-DuZJdKV_.js → Control-ZFh5ta_U.js} +1 -1
  3. package/dist/Datafair.client-CyZRNADr.js +30 -0
  4. package/dist/{Event--kp8kMdJ.js → Event-DSQcW7OF.js} +24 -24
  5. package/dist/{Image-34hvypZI.js → Image-BijNEG0p.js} +6 -6
  6. package/dist/JsonPreview.client-C9iaPSmQ.js +40 -0
  7. package/dist/{Map-BjUnLyj8.js → Map-BUtPf5GN.js} +756 -756
  8. package/dist/MapContainer.client-BuoZ69XO.js +101 -0
  9. package/dist/{OSM-s40W6sQ2.js → OSM-D4MTdBtk.js} +2 -2
  10. package/dist/{PdfPreview.client-BVjPxlPu.js → PdfPreview.client-MI0bDghc.js} +822 -865
  11. package/dist/{Pmtiles.client-CRJ56yX2.js → Pmtiles.client-CaKEYQBc.js} +574 -579
  12. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BKqb6TMw.js +61 -0
  13. package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-hJQIqcZm.js} +2 -2
  14. package/dist/{Tile-DbNFNPfU.js → Tile-Dcl7oIVu.js} +35 -35
  15. package/dist/{TileImage-BsXBxMtq.js → TileImage-BJeHipMX.js} +4 -4
  16. package/dist/{View-BR92hTWP.js → View-xp_P_OHw.js} +412 -401
  17. package/dist/XmlPreview.client-BVAeNK4n.js +34 -0
  18. package/dist/{common-PJfpC179.js → common-BjQlan3k.js} +36 -36
  19. package/dist/components-next.css +6 -6
  20. package/dist/components-next.js +166 -148
  21. package/dist/components.css +1 -1
  22. package/dist/{index-BZsAZ7iw.js → index-BBdS8QKx.js} +32886 -27183
  23. package/dist/{main-qc4CO9Kn.js → main-Dk_66g-3.js} +91331 -75844
  24. package/dist/{proj-DsetBcW7.js → proj-CsNo9yH1.js} +532 -512
  25. package/dist/{tilecoord-Db24Px13.js → tilecoord-A0fLnBZr.js} +28 -28
  26. package/dist/{vue3-xml-viewer.common-CCOV_ohP.js → vue3-xml-viewer.common-B8dNNkOU.js} +1 -1
  27. package/package.json +18 -11
  28. package/src/components/ActivityList/ActivityList.vue +0 -2
  29. package/src/components/Chart/ChartViewer.vue +226 -0
  30. package/src/components/Chart/ChartViewerWrapper.vue +170 -0
  31. package/src/components/Form/Listbox.vue +101 -0
  32. package/src/components/Form/SearchableSelect.vue +2 -1
  33. package/src/components/InfiniteLoader.vue +53 -0
  34. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  35. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  36. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  37. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  38. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  39. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  40. package/src/components/OpenApiViewer/openapi.ts +150 -0
  41. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  42. package/src/components/Pagination.vue +8 -5
  43. package/src/components/ReadMore.vue +1 -1
  44. package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
  45. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
  46. package/src/components/ResourceAccordion/MapContainer.client.vue +5 -14
  47. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  48. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -103
  49. package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
  50. package/src/components/ResourceAccordion/Preview.vue +16 -21
  51. package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
  52. package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
  53. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  54. package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -7
  55. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
  56. package/src/components/ResourceExplorer/ResourceExplorer.vue +81 -13
  57. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  58. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +30 -11
  59. package/src/components/Search/GlobalSearch.vue +191 -110
  60. package/src/components/Search/SearchInput.vue +5 -4
  61. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  62. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  63. package/src/components/TabularExplorer/TabularExplorer.vue +870 -0
  64. package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
  65. package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
  66. package/src/components/TabularExplorer/types.ts +83 -0
  67. package/src/composables/useHasTabularData.ts +6 -0
  68. package/src/composables/useResourceCapabilities.ts +1 -1
  69. package/src/composables/useSearchFilter.ts +118 -0
  70. package/src/composables/useStableQueryParams.ts +31 -3
  71. package/src/config.ts +3 -0
  72. package/src/functions/api.ts +34 -33
  73. package/src/functions/api.types.ts +1 -0
  74. package/src/functions/charts.ts +68 -0
  75. package/src/functions/datasets.ts +0 -17
  76. package/src/functions/resources.ts +56 -1
  77. package/src/functions/tabular.ts +60 -0
  78. package/src/functions/tabularApi.ts +138 -11
  79. package/src/main.ts +55 -7
  80. package/src/types/dataservices.ts +2 -0
  81. package/src/types/pages.ts +0 -5
  82. package/src/types/posts.ts +2 -2
  83. package/src/types/reports.ts +5 -1
  84. package/src/types/search.ts +52 -1
  85. package/src/types/site.ts +5 -3
  86. package/src/types/users.ts +2 -1
  87. package/src/types/visualizations.ts +89 -0
  88. package/assets/swagger-themes/newspaper.css +0 -1670
  89. package/dist/Datafair.client-0UYUu5yf.js +0 -35
  90. package/dist/JsonPreview.client-BrTMBWHZ.js +0 -87
  91. package/dist/MapContainer.client-CUmKyByc.js +0 -107
  92. package/dist/Swagger.client-2Yn7iF0A.js +0 -4
  93. package/dist/XmlPreview.client-DxqlVnKu.js +0 -79
  94. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  95. package/src/functions/pagination.ts +0 -9
  96. /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
@@ -56,7 +56,7 @@
56
56
  :resource
57
57
  />
58
58
  <RiSubtractLine
59
- v-if="resource.schema"
59
+ v-if="resource.schema?.name || resource.schema?.url"
60
60
  aria-hidden="true"
61
61
  class="size-3 fill-gray-medium"
62
62
  />
@@ -231,7 +231,7 @@
231
231
  :dataset="dataset"
232
232
  />
233
233
  <!-- Show Datafair embedded preview (koumoul) -->
234
- <SwaggerClient
234
+ <OpenApiViewer
235
235
  v-else-if="hasOpenAPIPreview"
236
236
  :url="resource.extras['apidocUrl'] as string"
237
237
  />
@@ -357,7 +357,7 @@
357
357
  <p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
358
358
  <p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
359
359
  </div>
360
- <Swagger
360
+ <OpenApiViewer
361
361
  v-if="hasTabularData"
362
362
  :url="`${config.tabularApiUrl}/api/resources/${props.resource.id}/swagger/`"
363
363
  />
@@ -387,9 +387,8 @@ import { trackEvent } from '../../functions/matomo'
387
387
  import CopyButton from '../CopyButton.vue'
388
388
  import { useComponentsConfig } from '../../config'
389
389
  import { getOwnerName } from '../../functions/owned'
390
- import { getResourceFormatIcon, getResourceTitleId, detectOgcService } from '../../functions/resources'
390
+ import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize } from '../../functions/resources'
391
391
  import BrandedButton from '../BrandedButton.vue'
392
- import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
393
392
  import { useTranslation } from '../../composables/useTranslation'
394
393
  import { useHasTabularData } from '../../composables/useHasTabularData'
395
394
  import Metadata from './Metadata.vue'
@@ -399,7 +398,7 @@ import EditButton from './EditButton.vue'
399
398
  import DataStructure from './DataStructure.vue'
400
399
  import Preview from './Preview.vue'
401
400
  import { isOrganizationCertified } from '../../functions/organizations'
402
- import SwaggerClient from './Swagger.client.vue'
401
+ import OpenApiViewer from '../OpenApiViewer/OpenApiViewer.vue'
403
402
 
404
403
  const GENERATED_FORMATS = ['parquet', 'pmtiles', 'geojson']
405
404
  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']
@@ -418,7 +417,6 @@ const props = withDefaults(defineProps<{
418
417
 
419
418
  const config = useComponentsConfig()
420
419
 
421
- const Swagger = defineAsyncComponent(() => import('./Swagger.client.vue'))
422
420
  const MapContainer = defineAsyncComponent(() => import('./MapContainer.client.vue'))
423
421
  const Pmtiles = defineAsyncComponent(() => import('./Pmtiles.client.vue'))
424
422
  const JsonPreview = defineAsyncComponent(() => import('./JsonPreview.client.vue'))
@@ -1,59 +1,24 @@
1
1
  <template>
2
- <div class="fr-text--xs">
3
- <div v-if="xmlData">
4
- <XmlViewer :xml="xmlData" />
5
- </div>
6
- <div
7
- v-else-if="loading"
8
- class="text-gray-medium"
9
- >
10
- {{ t("Chargement de l'aperçu XML...") }}
11
- </div>
12
- <SimpleBanner
13
- v-else-if="fileTooLarge"
14
- type="warning"
15
- class="flex items-center space-x-2"
16
- >
17
- <RiErrorWarningLine class="shrink-0 size-6" />
18
- <span>{{ fileSizeBytes
19
- ? t("Fichier XML trop volumineux pour l'aperçu. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
20
- : t("L'aperçu n'est pas disponible car la taille du fichier est inconnue. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
21
- }}</span>
22
- </SimpleBanner>
23
- <SimpleBanner
24
- v-else-if="error === 'network'"
25
- type="warning"
26
- class="flex items-center space-x-2"
27
- >
28
- <RiErrorWarningLine class="shrink-0 size-6" />
29
- <span>{{ t("Ce fichier XML ne peut pas être prévisualisé, peut-être parce qu'il est hébergé sur un autre site qui ne l'autorise pas. Pour le consulter, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.") }}</span>
30
- </SimpleBanner>
31
- <SimpleBanner
32
- v-else-if="error"
33
- type="warning"
34
- class="flex items-center space-x-2"
35
- >
36
- <RiErrorWarningLine class="shrink-0 size-6" />
37
- <span>{{ t("Erreur lors du chargement de l'aperçu XML.") }}</span>
38
- </SimpleBanner>
39
- </div>
2
+ <PreviewWrapper
3
+ v-slot="{ data }"
4
+ file-type="XML"
5
+ :resource="resource"
6
+ :max-size="config.maxXmlPreviewCharSize"
7
+ :load="load"
8
+ >
9
+ <XmlViewer :xml="(data as string)" />
10
+ </PreviewWrapper>
40
11
  </template>
41
12
 
42
13
  <script setup lang="ts">
43
- import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
44
- import { RiErrorWarningLine } from '@remixicon/vue'
45
-
14
+ import { defineAsyncComponent } from 'vue'
46
15
  import { useComponentsConfig } from '../../config'
47
- import SimpleBanner from '../SimpleBanner.vue'
16
+ import PreviewWrapper from './PreviewWrapper.vue'
48
17
  import type { Resource } from '../../types/resources'
49
- import { useTranslation } from '../../composables/useTranslation'
50
18
  import '../../types/vue3-xml-viewer.d'
51
- import { getResourceFilesize } from '../../main'
52
19
 
53
20
  const XmlViewer = defineAsyncComponent(() =>
54
- import('vue3-xml-viewer').then((module) => {
55
- return module.default || module.XmlViewer
56
- }),
21
+ import('vue3-xml-viewer').then(module => module.default || module.XmlViewer),
57
22
  )
58
23
 
59
24
  const props = defineProps<{
@@ -61,74 +26,10 @@ const props = defineProps<{
61
26
  }>()
62
27
 
63
28
  const config = useComponentsConfig()
64
- const { t } = useTranslation()
65
-
66
- const xmlData = ref<string | null>(null)
67
- const loading = ref(false)
68
- const error = ref<string | null>(null)
69
- const fileTooLarge = ref(false)
70
-
71
- const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
72
-
73
- const shouldLoadXml = computed(() => {
74
- const size = fileSizeBytes.value
75
- if (!size) {
76
- // If we don't know the size, don't risk loading a potentially huge file
77
- return false
78
- }
79
-
80
- // Check if maxXmlPreviewCharSize is configured
81
- if (!config.maxXmlPreviewCharSize) {
82
- // If no limit is set, don't load unknown files
83
- return false
84
- }
85
-
86
- // Convert maxXmlPreviewCharSize from characters to bytes (rough estimate)
87
- // Assuming average 1 byte per character for XML
88
- const maxByteSize = config.maxXmlPreviewCharSize
89
-
90
- return size <= maxByteSize
91
- })
92
29
 
93
- const fetchXmlData = async () => {
94
- // Check if file is too large or size is unknown before making the request
95
- if (!shouldLoadXml.value) {
96
- fileTooLarge.value = true
97
- return
98
- }
99
-
100
- loading.value = true
101
- error.value = null
102
-
103
- try {
104
- const response = await fetch(props.resource.url)
105
- // const response = await fetch('/test-data.xml') // For testing locally without CORS issues
106
- if (!response.ok) {
107
- throw new Error(`HTTP error! status: ${response.status}`)
108
- }
109
- const data = await response.text()
110
-
111
- // Use the XML data as string - let the XML viewer handle large files
112
- xmlData.value = data
113
- }
114
- catch (err) {
115
- console.error('Error loading XML:', err)
116
-
117
- if (err instanceof TypeError) {
118
- error.value = 'network'
119
- }
120
- else {
121
- error.value = 'generic'
122
- }
123
-
124
- xmlData.value = null
125
- }
126
- finally {
127
- loading.value = false
128
- }
30
+ const load = async () => {
31
+ const response = await fetch(props.resource.url)
32
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
33
+ return response.text()
129
34
  }
130
-
131
- onMounted(() => {
132
- fetchXmlData()
133
- })
134
35
  </script>
@@ -2,6 +2,19 @@
2
2
  <div v-if="allResources.length || hasAnyResources">
3
3
  <div class="flex gap-6">
4
4
  <div class="flex-1 min-w-0">
5
+ <div
6
+ v-if="dataset.resources.total > 1"
7
+ class="md:hidden flex justify-end mb-3"
8
+ >
9
+ <BrandedButton
10
+ size="xs"
11
+ color="secondary"
12
+ :icon="RiListUnordered"
13
+ @click="mobileSidebarOpen = true"
14
+ >
15
+ {{ t('Ressources ({count})', { count: dataset.resources.total }) }}
16
+ </BrandedButton>
17
+ </div>
5
18
  <ResourceExplorerViewer
6
19
  v-if="selectedResource && allResources.length"
7
20
  :key="selectedResource.id"
@@ -30,17 +43,38 @@
30
43
  </BrandedButton>
31
44
  </div>
32
45
  </div>
46
+ <div class="hidden md:block">
47
+ <ResourceExplorerSidebar
48
+ :resources="allResources"
49
+ :selected-resource-id="selectedResource?.id ?? null"
50
+ :collapsed="sidebarCollapsed"
51
+ :search
52
+ @select="selectResource"
53
+ @load-more="loadMore"
54
+ @update:collapsed="sidebarCollapsed = $event"
55
+ @update:search="updateSearch($event)"
56
+ />
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Mobile sidebar panel -->
61
+ <dialog
62
+ ref="mobileSidebarDialog"
63
+ class="mobile-sidebar md:hidden fixed inset-0 m-0 ml-auto p-0 h-dvh max-h-dvh w-80 max-w-[85vw] bg-white shadow-lg overflow-y-auto overscroll-contain backdrop:bg-black/30"
64
+ @close="mobileSidebarOpen = false"
65
+ @click.self="closeMobileSidebar"
66
+ >
33
67
  <ResourceExplorerSidebar
34
68
  :resources="allResources"
35
69
  :selected-resource-id="selectedResource?.id ?? null"
36
- :collapsed="sidebarCollapsed"
70
+ :collapsed="false"
37
71
  :search
38
72
  @select="selectResource"
39
73
  @load-more="loadMore"
40
- @update:collapsed="sidebarCollapsed = $event"
74
+ @update:collapsed="closeMobileSidebar"
41
75
  @update:search="updateSearch($event)"
42
76
  />
43
- </div>
77
+ </dialog>
44
78
  </div>
45
79
  <div
46
80
  v-else
@@ -73,6 +107,7 @@ import type { DatasetV2 } from '../../types/datasets'
73
107
  import type { Resource, ResourceGroup, ResourceType } from '../../types/resources'
74
108
  import ResourceExplorerSidebar from './ResourceExplorerSidebar.vue'
75
109
  import ResourceExplorerViewer from './ResourceExplorerViewer.vue'
110
+ import { RiListUnordered } from '@remixicon/vue'
76
111
  import BrandedButton from '../BrandedButton.vue'
77
112
 
78
113
  const props = withDefaults(defineProps<{
@@ -159,6 +194,22 @@ watch(searchDebounced, () => {
159
194
  }
160
195
  })
161
196
 
197
+ // Separate useFetch for loadMore, initialized at setup time with immediate: false
198
+ // so that it doesn't fetch until execute() is called from the event handler.
199
+ const loadMoreType = ref<ResourceType>('main')
200
+ const loadMorePage = ref(1)
201
+ const loadMoreParams = computed(() => ({
202
+ type: loadMoreType.value,
203
+ page_size: PAGE_SIZE,
204
+ page: loadMorePage.value,
205
+ q: searchDebounced.value || undefined,
206
+ }))
207
+ const { data: loadMoreData, execute: executeLoadMore } = await useFetch<PaginatedArray<Resource>>(url, {
208
+ params: loadMoreParams,
209
+ immediate: false,
210
+ watch: false,
211
+ })
212
+
162
213
  const loadMore = async (type: ResourceType) => {
163
214
  const index = RESOURCE_TYPE.indexOf(type)
164
215
  if (index === -1) return
@@ -166,17 +217,12 @@ const loadMore = async (type: ResourceType) => {
166
217
  const extraRef = extraResourcesByType[index]!
167
218
  pageRef.value++
168
219
 
169
- const { data } = await useFetch<PaginatedArray<Resource>>(url, {
170
- params: {
171
- type,
172
- page_size: PAGE_SIZE,
173
- page: pageRef.value,
174
- q: searchDebounced.value || undefined,
175
- },
176
- })
220
+ loadMoreType.value = type
221
+ loadMorePage.value = pageRef.value
222
+ await executeLoadMore()
177
223
 
178
- if (data.value) {
179
- extraRef.value = [...extraRef.value, ...data.value.data]
224
+ if (loadMoreData.value) {
225
+ extraRef.value = [...extraRef.value, ...loadMoreData.value.data]
180
226
  }
181
227
  }
182
228
 
@@ -200,6 +246,21 @@ const flatResources = computed(() =>
200
246
 
201
247
  // Fetch resource by ID if specified in URL (for SSR)
202
248
  const initialResourceId = resourceIdQuery.value
249
+ const mobileSidebarOpen = ref(false)
250
+ const mobileSidebarDialog = ref<HTMLDialogElement | null>(null)
251
+
252
+ watch(mobileSidebarOpen, (open) => {
253
+ if (open) {
254
+ mobileSidebarDialog.value?.showModal()
255
+ }
256
+ else {
257
+ mobileSidebarDialog.value?.close()
258
+ }
259
+ })
260
+
261
+ function closeMobileSidebar() {
262
+ mobileSidebarOpen.value = false
263
+ }
203
264
  const { data: fetchedResource } = initialResourceId
204
265
  ? await useFetch<Resource>(`/api/1/datasets/${props.dataset.id}/resources/${initialResourceId}/`)
205
266
  : { data: ref(null) }
@@ -227,6 +288,7 @@ function updateSearch(newSearch: string) {
227
288
 
228
289
  const selectResource = (resource: Resource) => {
229
290
  selectedResource.value = resource
291
+ mobileSidebarOpen.value = false
230
292
  router.replace({
231
293
  query: { ...router.currentRoute.value.query, resource_id: resource.id },
232
294
  })
@@ -241,3 +303,9 @@ watch(flatResources, () => {
241
303
  }
242
304
  })
243
305
  </script>
306
+
307
+ <style>
308
+ html:has(dialog.mobile-sidebar[open]) {
309
+ overflow: hidden;
310
+ }
311
+ </style>
@@ -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"
@@ -6,7 +6,7 @@
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>
@@ -14,9 +14,16 @@
14
14
  :label="t('Copier le lien')"
15
15
  :copied-label="t('Lien copié !')"
16
16
  :text="resourceExternalUrl"
17
+ class="hidden md:inline-flex"
17
18
  />
18
19
  </div>
19
- <div class="text-gray-medium text-xs flex items-center gap-1">
20
+ <div class="text-gray-medium text-xs flex items-center gap-1 flex-wrap">
21
+ <SchemaBadge :resource />
22
+ <RiSubtractLine
23
+ v-if="resource.schema?.name || resource.schema?.url"
24
+ aria-hidden="true"
25
+ class="size-3 fill-gray-medium"
26
+ />
20
27
  <span>{{ t('mis à jour {date}', { date: formatRelativeIfRecentDate(resource.last_modified) }) }}</span>
21
28
  <RiSubtractLine
22
29
  aria-hidden="true"
@@ -128,14 +135,28 @@
128
135
  :resource="resource"
129
136
  :dataset="dataset"
130
137
  />
131
- <SwaggerClient
138
+ <OpenApiViewer
132
139
  v-else-if="hasOpenAPIPreview"
133
140
  :url="resource.extras['apidocUrl'] as string"
134
141
  />
135
142
  <Preview
136
- v-else
143
+ v-else-if="hasTabularData"
137
144
  :resource="resource"
138
145
  />
146
+ <PreviewUnavailable v-else>
147
+ <!-- "File too large to download" is the only analysis:error value from hydra for now -->
148
+ <template v-if="resource.extras['analysis:error'] === 'File too large to download'">
149
+ {{ t("Ce fichier est trop volumineux pour être analysé et prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.") }}
150
+ </template>
151
+ <template v-else-if="resource.extras['analysis:parsing:error']">
152
+ {{ 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.") }}
153
+ <br>
154
+ <span class="text-gray-medium text-xs">{{ resource.extras['analysis:parsing:error'] }}</span>
155
+ </template>
156
+ <template v-else>
157
+ {{ t("Ce fichier ne peut pas être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.") }}
158
+ </template>
159
+ </PreviewUnavailable>
139
160
  </div>
140
161
  <div v-if="tab.key === 'description'">
141
162
  <MarkdownViewer
@@ -283,7 +304,7 @@
283
304
  <p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
284
305
  <p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
285
306
  </div>
286
- <Swagger
307
+ <OpenApiViewer
287
308
  v-if="hasTabularData"
288
309
  :url="`${config.tabularApiUrl}/api/resources/${resource.id}/swagger/`"
289
310
  />
@@ -298,12 +319,13 @@
298
319
  <script setup lang="ts">
299
320
  import { computed, defineAsyncComponent } from 'vue'
300
321
  import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiInformationLine, RiSubtractLine } from '@remixicon/vue'
322
+ import PreviewUnavailable from '../ResourceAccordion/PreviewUnavailable.vue'
301
323
  import { toast } from 'vue-sonner'
302
324
  import BrandedButton from '../BrandedButton.vue'
303
325
  import CopyButton from '../CopyButton.vue'
304
326
  import MarkdownViewer from '../MarkdownViewer.vue'
305
327
  import ResourceIcon from '../ResourceAccordion/ResourceIcon.vue'
306
- import Swagger from '../ResourceAccordion/Swagger.client.vue'
328
+ import OpenApiViewer from '../OpenApiViewer/OpenApiViewer.vue'
307
329
  import TabGroup from '../Tabs/TabGroup.vue'
308
330
  import TabList from '../Tabs/TabList.vue'
309
331
  import Tab from '../Tabs/Tab.vue'
@@ -313,9 +335,9 @@ import Tooltip from '../Tooltip.vue'
313
335
  import Preview from '../ResourceAccordion/Preview.vue'
314
336
  import DataStructure from '../ResourceAccordion/DataStructure.vue'
315
337
  import Metadata from '../ResourceAccordion/Metadata.vue'
338
+ import SchemaBadge from '../ResourceAccordion/SchemaBadge.vue'
316
339
  import { filesize, summarize } from '../../functions/helpers'
317
- import { getResourceFormatIcon } from '../../functions/resources'
318
- import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
340
+ import { getResourceFormatIcon, getResourceExternalUrl, getResourceFilesize } from '../../functions/resources'
319
341
  import { trackEvent } from '../../functions/matomo'
320
342
  import { useComponentsConfig } from '../../config'
321
343
  import { useFormatDate } from '../../functions/dates'
@@ -342,9 +364,6 @@ const MapContainer = defineAsyncComponent(() =>
342
364
  const Pmtiles = defineAsyncComponent(() =>
343
365
  import('../ResourceAccordion/Pmtiles.client.vue'),
344
366
  )
345
- const SwaggerClient = defineAsyncComponent(() =>
346
- import('../ResourceAccordion/Swagger.client.vue'),
347
- )
348
367
 
349
368
  const props = defineProps<{
350
369
  dataset: Dataset | DatasetV2