@datagouv/components-next 1.0.2-dev.16 → 1.0.2-dev.161

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 (126) 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-Bz1l4SLK.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/ImagePreview.client-CogDiX0n.js +36 -0
  8. package/dist/JsonPreview.client-XwLb74J8.js +40 -0
  9. package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
  10. package/dist/{MapContainer.client-DB6p6KF8.js → MapContainer.client-DJm7fYDx.js} +38 -39
  11. package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
  12. package/dist/{PdfPreview.client-Dr4VcIur.js → PdfPreview.client-CoLz0aZp.js} +823 -855
  13. package/dist/Pmtiles.client-bKmHL2Qg.js +26324 -0
  14. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-CxXJ7rC5.js +58 -0
  15. package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-VGQ7nkMS.js} +2 -2
  16. package/dist/{Tile-DbNFNPfU.js → Tile-CSpn0Ca8.js} +160 -163
  17. package/dist/{TileImage-BsXBxMtq.js → TileImage-CpbaEFwd.js} +143 -131
  18. package/dist/{View-BR92hTWP.js → View-BYBpt-AU.js} +483 -422
  19. package/dist/XmlPreview.client-Baetgqam.js +34 -0
  20. package/dist/{common-PJfpC179.js → common-BoeTUnDZ.js} +52 -52
  21. package/dist/components-next.css +1 -52
  22. package/dist/components-next.js +182 -146
  23. package/dist/components.css +2 -2
  24. package/dist/{index-D3rieddI.js → index-Crp28Eov.js} +21834 -15810
  25. package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-CRXb3b2x.js} +185 -187
  26. package/dist/main-Cmq8fQdM.js +73989 -0
  27. package/dist/{proj-DsetBcW7.js → proj-DxGbobp7.js} +662 -626
  28. package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
  29. package/dist/{tilecoord-Db24Px13.js → tilecoord-DvX7WWiA.js} +273 -264
  30. package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
  31. package/dist/{vue3-xml-viewer.common-CJXShyPB.js → vue3-xml-viewer.common-H2czi46-.js} +16 -16
  32. package/package.json +50 -37
  33. package/src/chart.ts +5 -0
  34. package/src/components/ActivityList/ActivityList.vue +3 -2
  35. package/src/components/Chart/ChartViewer.vue +320 -0
  36. package/src/components/Chart/ChartViewerWrapper.vue +182 -0
  37. package/src/components/DataserviceCard.vue +9 -0
  38. package/src/components/DataserviceQuality.vue +82 -0
  39. package/src/components/DataserviceQualityInline.vue +31 -0
  40. package/src/components/DataserviceQualityTooltipContent.vue +79 -0
  41. package/src/components/DatasetCard.vue +9 -4
  42. package/src/components/DatasetQuality.vue +11 -11
  43. package/src/components/DatasetQualityInline.vue +2 -2
  44. package/src/components/DatasetQualityTooltipContent.vue +1 -1
  45. package/src/components/Form/Listbox.vue +101 -0
  46. package/src/components/Form/SearchableSelect.vue +2 -1
  47. package/src/components/InfiniteLoader.vue +53 -0
  48. package/src/components/ObjectCardHeader.vue +11 -4
  49. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  50. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  51. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  52. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  53. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  54. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  55. package/src/components/OpenApiViewer/openapi.ts +150 -0
  56. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  57. package/src/components/Pagination.vue +8 -5
  58. package/src/components/RadioInput.vue +7 -2
  59. package/src/components/ResourceAccordion/DataStructure.vue +11 -33
  60. package/src/components/ResourceAccordion/Downloads.vue +160 -0
  61. package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
  62. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
  63. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -3
  64. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  65. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -87
  66. package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -1
  67. package/src/components/ResourceAccordion/Preview.vue +11 -11
  68. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  69. package/src/components/ResourceAccordion/ResourceAccordion.vue +19 -113
  70. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
  71. package/src/components/ResourceExplorer/ResourceExplorer.vue +22 -19
  72. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  73. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +64 -148
  74. package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
  75. package/src/components/ReuseCard.vue +14 -6
  76. package/src/components/ReuseDetails.vue +2 -2
  77. package/src/components/ReuseHorizontalCard.vue +2 -2
  78. package/src/components/Search/GlobalSearch.vue +219 -117
  79. package/src/components/Search/SearchInput.vue +5 -4
  80. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  81. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  82. package/src/components/TabularExplorer/TabularExplorer.vue +1132 -0
  83. package/src/components/TabularExplorer/TabularFilterContent.vue +353 -0
  84. package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
  85. package/src/components/TabularExplorer/types.ts +86 -0
  86. package/src/composables/useHasTabularData.ts +15 -1
  87. package/src/composables/useMetrics.ts +1 -1
  88. package/src/composables/useResourceCapabilities.ts +28 -15
  89. package/src/composables/useSearchFilter.ts +120 -0
  90. package/src/composables/useStableQueryParams.ts +38 -6
  91. package/src/composables/useTabularProfile.ts +70 -0
  92. package/src/config.ts +21 -3
  93. package/src/functions/activities.ts +3 -3
  94. package/src/functions/api.ts +20 -42
  95. package/src/functions/api.types.ts +1 -0
  96. package/src/functions/charts.ts +95 -0
  97. package/src/functions/datasets.ts +0 -17
  98. package/src/functions/dates.ts +3 -2
  99. package/src/functions/metrics.ts +6 -4
  100. package/src/functions/resources.ts +117 -1
  101. package/src/functions/tabular.ts +171 -0
  102. package/src/functions/tabularApi.ts +96 -11
  103. package/src/main.ts +107 -14
  104. package/src/types/dataservices.ts +17 -0
  105. package/src/types/organizations.ts +10 -0
  106. package/src/types/pages.ts +0 -5
  107. package/src/types/posts.ts +2 -2
  108. package/src/types/reports.ts +5 -1
  109. package/src/types/resources.ts +0 -1
  110. package/src/types/reuses.ts +12 -1
  111. package/src/types/search.ts +63 -1
  112. package/src/types/site.ts +5 -3
  113. package/src/types/ui.ts +2 -0
  114. package/src/types/users.ts +2 -1
  115. package/src/types/visualizations.ts +93 -0
  116. package/assets/swagger-themes/newspaper.css +0 -1670
  117. package/dist/JsonPreview.client-DojrmGyU.js +0 -72
  118. package/dist/Pmtiles.client-DbMjfK00.js +0 -25185
  119. package/dist/Swagger.client-Db_xYKJv.js +0 -4
  120. package/dist/XmlPreview.client-CHxSFhd7.js +0 -64
  121. package/dist/main-D70Xt4a7.js +0 -105854
  122. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  123. package/src/functions/pagination.ts +0 -9
  124. /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
  125. /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
  126. /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div class="text-xs">
3
+ <slot
4
+ v-if="data !== null"
5
+ :data="data"
6
+ />
7
+ <div
8
+ v-else-if="loading"
9
+ class="text-gray-medium"
10
+ >
11
+ {{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
12
+ </div>
13
+ <PreviewUnavailable v-else-if="!isSizeAllowed">
14
+ {{ 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.")
17
+ }}
18
+ </PreviewUnavailable>
19
+ <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 }) }}
21
+ </PreviewUnavailable>
22
+ <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.") }}
24
+ </PreviewUnavailable>
25
+ <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.") }}
27
+ </PreviewUnavailable>
28
+ </div>
29
+ </template>
30
+
31
+ <script setup lang="ts">
32
+ import { computed, nextTick, onMounted, ref } from 'vue'
33
+ import PreviewUnavailable from './PreviewUnavailable.vue'
34
+ import type { Resource } from '../../types/resources'
35
+ import { getResourceFilesize, getResourceCorsStatus } from '../../functions/resources'
36
+ import { useTranslation } from '../../composables/useTranslation'
37
+
38
+ const props = defineProps<{
39
+ fileType: string
40
+ resource: Resource
41
+ maxSize: number | undefined
42
+ load: () => Promise<unknown>
43
+ }>()
44
+
45
+ const emit = defineEmits<{
46
+ loaded: []
47
+ }>()
48
+
49
+ const { t } = useTranslation()
50
+
51
+ const data = ref<unknown>(null)
52
+ const loading = ref(false)
53
+ const error = ref<'network' | 'generic' | null>(null)
54
+
55
+ const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
56
+ const corsStatus = computed(() => getResourceCorsStatus(props.resource))
57
+
58
+ const isSizeAllowed = computed(() => {
59
+ const size = fileSizeBytes.value
60
+ const max = props.maxSize
61
+ if (!size || !max) return false
62
+ return size <= max
63
+ })
64
+
65
+ onMounted(async () => {
66
+ if (!isSizeAllowed.value || corsStatus.value === 'blocked') return
67
+
68
+ loading.value = true
69
+ try {
70
+ data.value = await props.load()
71
+ await nextTick()
72
+ emit('loaded')
73
+ }
74
+ catch (err) {
75
+ console.error('Error loading preview:', err)
76
+ error.value = err instanceof TypeError ? 'network' : 'generic'
77
+ }
78
+ finally {
79
+ loading.value = false
80
+ }
81
+ })
82
+ </script>
@@ -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
  />
@@ -224,6 +224,10 @@
224
224
  v-else-if="resource.format && resource.format.toLowerCase() === 'xml'"
225
225
  :resource="resource"
226
226
  />
227
+ <ImagePreview
228
+ v-else-if="isImagePreviewFormat(resource.format)"
229
+ :resource="resource"
230
+ />
227
231
  <!-- Show Datafair embedded preview (koumoul) -->
228
232
  <DatafairPreview
229
233
  v-else-if="hasDatafairPreview"
@@ -231,7 +235,7 @@
231
235
  :dataset="dataset"
232
236
  />
233
237
  <!-- Show Datafair embedded preview (koumoul) -->
234
- <SwaggerClient
238
+ <OpenApiViewer
235
239
  v-else-if="hasOpenAPIPreview"
236
240
  :url="resource.extras['apidocUrl'] as string"
237
241
  />
@@ -265,91 +269,13 @@
265
269
  <div
266
270
  v-if="tab.key === 'downloads'"
267
271
  >
268
- <dl class="fr-pl-0">
269
- <dt
270
- v-if="resource.format === 'url'"
271
- class="font-bold fr-text--sm fr-mb-0"
272
- >
273
- {{ t("URL d'origine") }}
274
- </dt>
275
- <dt
276
- v-else
277
- class="font-bold fr-text--sm fr-mb-0"
278
- >
279
- {{ t('Format original') }}
280
- </dt>
281
- <dd class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center">
282
- <span v-if="resource.format === 'url'">
283
- <a
284
- :href="resource.latest"
285
- class="fr-link no-icon-after"
286
- rel="ugc nofollow noopener"
287
- target="_blank"
288
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
289
- >
290
- <component
291
- :is="config.textClamp"
292
- v-if="config && config.textClamp"
293
- :auto-resize="true"
294
- :max-lines="1"
295
- :text="resource.url"
296
- >
297
- <template #after>
298
- <span class="fr-ml-1v fr-icon-external-link-line fr-icon--sm" />
299
- </template>
300
- </component>
301
- </a>
302
- </span>
303
- <span v-else>
304
- <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
305
- <a
306
- :href="resource.latest"
307
- class="fr-link"
308
- rel="ugc nofollow noopener"
309
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${resource.format}`)"
310
- >
311
- <span>{{ t('Format {format}', { format: resource.format }) }}<span v-if="resourceFilesize"> - {{ filesize(resourceFilesize) }}</span></span>
312
- </a>
313
- </span>
314
- <CopyButton
315
- :label="t('Copier le lien')"
316
- :copied-label="t('Lien copié !')"
317
- :text="resource.latest"
318
- class="relative"
319
- />
320
- </dd>
321
- <template v-if="generatedFormats.length">
322
- <dt class="font-bold fr-text--sm fr-mb-0">
323
- {{ t('Formats générés automatiquement par {platform} (dernière mise à jour {date})', { platform: config.name, date: conversionsLastUpdate }) }}
324
- </dt>
325
- <dd
326
- v-for="generatedFormat in generatedFormats"
327
- :key="generatedFormat.format"
328
- class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
329
- >
330
- <span>
331
- <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
332
- <a
333
- :href="generatedFormat.url"
334
- class="fr-link"
335
- rel="ugc nofollow noopener"
336
- @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${generatedFormat.format}`)"
337
- >
338
- <span>{{ t('Format {format}', { format: generatedFormat.format }) }}<span v-if="generatedFormat.size"> - {{ filesize(generatedFormat.size) }}</span></span>
339
- </a>
340
- </span>
341
- <CopyButton
342
- :label="t('Copier le lien')"
343
- :copied-label="t('Lien copié !')"
344
- :text="generatedFormat.url"
345
- class="relative"
346
- />
347
- </dd>
348
- </template>
349
- </dl>
272
+ <Downloads
273
+ :resource="resource"
274
+ :dataset="dataset"
275
+ />
350
276
  </div>
351
277
  <div
352
- v-if="tab.key === 'swagger'"
278
+ v-if="tab.key === 'api'"
353
279
  >
354
280
  <div class="fr-mb-4w">
355
281
  <p>{{ t("Cette API est générée automatiquement par {platform} à partir du fichier.", { platform: config.name }) }}</p>
@@ -357,7 +283,7 @@
357
283
  <p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
358
284
  <p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
359
285
  </div>
360
- <Swagger
286
+ <OpenApiViewer
361
287
  v-if="hasTabularData"
362
288
  :url="`${config.tabularApiUrl}/api/resources/${props.resource.id}/swagger/`"
363
289
  />
@@ -387,9 +313,8 @@ import { trackEvent } from '../../functions/matomo'
387
313
  import CopyButton from '../CopyButton.vue'
388
314
  import { useComponentsConfig } from '../../config'
389
315
  import { getOwnerName } from '../../functions/owned'
390
- import { getResourceFormatIcon, getResourceTitleId, detectOgcService } from '../../functions/resources'
316
+ import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize, isImagePreviewFormat } from '../../functions/resources'
391
317
  import BrandedButton from '../BrandedButton.vue'
392
- import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
393
318
  import { useTranslation } from '../../composables/useTranslation'
394
319
  import { useHasTabularData } from '../../composables/useHasTabularData'
395
320
  import Metadata from './Metadata.vue'
@@ -397,11 +322,11 @@ import SchemaBadge from './SchemaBadge.vue'
397
322
  import ResourceIcon from './ResourceIcon.vue'
398
323
  import EditButton from './EditButton.vue'
399
324
  import DataStructure from './DataStructure.vue'
325
+ import Downloads from './Downloads.vue'
400
326
  import Preview from './Preview.vue'
401
327
  import { isOrganizationCertified } from '../../functions/organizations'
402
- import SwaggerClient from './Swagger.client.vue'
328
+ import OpenApiViewer from '../OpenApiViewer/OpenApiViewer.vue'
403
329
 
404
- const GENERATED_FORMATS = ['parquet', 'pmtiles', 'geojson']
405
330
  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']
406
331
 
407
332
  const props = withDefaults(defineProps<{
@@ -418,12 +343,12 @@ const props = withDefaults(defineProps<{
418
343
 
419
344
  const config = useComponentsConfig()
420
345
 
421
- const Swagger = defineAsyncComponent(() => import('./Swagger.client.vue'))
422
346
  const MapContainer = defineAsyncComponent(() => import('./MapContainer.client.vue'))
423
347
  const Pmtiles = defineAsyncComponent(() => import('./Pmtiles.client.vue'))
424
348
  const JsonPreview = defineAsyncComponent(() => import('./JsonPreview.client.vue'))
425
349
  const PdfPreview = defineAsyncComponent(() => import('./PdfPreview.client.vue'))
426
350
  const XmlPreview = defineAsyncComponent(() => import('./XmlPreview.client.vue'))
351
+ const ImagePreview = defineAsyncComponent(() => import('./ImagePreview.client.vue'))
427
352
  const DatafairPreview = defineAsyncComponent(() => import('./Datafair.client.vue'))
428
353
 
429
354
  const { t } = useTranslation()
@@ -431,11 +356,11 @@ const { formatRelativeIfRecentDate } = useFormatDate()
431
356
  const checkTabularData = useHasTabularData()
432
357
 
433
358
  const hasPreview = computed(() => {
434
- // For JSON, PDF, and XML files, show preview.
359
+ // For JSON, PDF, XML, and image files, show preview.
435
360
  // We cannot check for CORS issues here because we cannot use an async component here.
436
361
  // 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.
437
362
  const format = props.resource.format?.toLowerCase()
438
- return format === 'json' || format === 'pdf' || format === 'xml'
363
+ return format === 'json' || format === 'pdf' || format === 'xml' || isImagePreviewFormat(format)
439
364
  })
440
365
 
441
366
  const hasTabularData = computed(() => checkTabularData(props.resource))
@@ -462,24 +387,6 @@ const ogcService = computed(() => detectOgcService(props.resource))
462
387
 
463
388
  const ogcWms = computed(() => ogcService.value === 'wms')
464
389
 
465
- const generatedFormats = computed(() => {
466
- const formats = GENERATED_FORMATS
467
- .filter(format => `analysis:parsing:${format}_url` in props.resource.extras)
468
- .map(format => ({
469
- url: props.resource.extras[`analysis:parsing:${format}_url`] as string,
470
- size: props.resource.extras[`analysis:parsing:${format}_size`] as number | undefined,
471
- format: format,
472
- }))
473
- if ('analysis:parsing:parsing_table' in props.resource.extras) {
474
- formats.push({
475
- url: `${config.tabularApiUrl}/api/resources/${props.resource.id}/data/json/`,
476
- size: undefined,
477
- format: 'json',
478
- })
479
- }
480
- return formats
481
- })
482
-
483
390
  const open = ref(props.expandedOnMount)
484
391
  const toggle = () => {
485
392
  open.value = !open.value
@@ -515,7 +422,7 @@ const tabsOptions = computed(() => {
515
422
  options.push({ key: 'downloads', label: t('Téléchargements') })
516
423
 
517
424
  if (hasTabularData.value) {
518
- options.push({ key: 'swagger', label: t('Swagger') })
425
+ options.push({ key: 'api', label: t('API') })
519
426
  }
520
427
 
521
428
  return options
@@ -542,7 +449,6 @@ const communityResource = computed<CommunityResource | null>(() => {
542
449
  const owner = computed(() => communityResource.value ? getOwnerName(communityResource.value) : null)
543
450
 
544
451
  const lastUpdate = props.resource.last_modified
545
- const conversionsLastUpdate = computed(() => formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined))
546
452
  const availabilityChecked = props.resource.extras && 'check:available' in props.resource.extras
547
453
  const resourceFilesize = computed(() => getResourceFilesize(props.resource))
548
454
 
@@ -1,42 +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
- <PreviewUnavailable v-else-if="fileTooLarge">
13
- {{ fileSizeBytes
14
- ? t("Le fichier XML est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.")
15
- : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
16
- }}
17
- </PreviewUnavailable>
18
- <PreviewUnavailable v-else-if="error === 'network'">
19
- {{ 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.") }}
20
- </PreviewUnavailable>
21
- <PreviewUnavailable v-else-if="error">
22
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
23
- </PreviewUnavailable>
24
- </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>
25
11
  </template>
26
12
 
27
13
  <script setup lang="ts">
28
- import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
14
+ import { defineAsyncComponent } from 'vue'
29
15
  import { useComponentsConfig } from '../../config'
30
- import PreviewUnavailable from './PreviewUnavailable.vue'
16
+ import PreviewWrapper from './PreviewWrapper.vue'
31
17
  import type { Resource } from '../../types/resources'
32
- import { useTranslation } from '../../composables/useTranslation'
33
18
  import '../../types/vue3-xml-viewer.d'
34
- import { getResourceFilesize } from '../../main'
35
19
 
36
20
  const XmlViewer = defineAsyncComponent(() =>
37
- import('vue3-xml-viewer').then((module) => {
38
- return module.default || module.XmlViewer
39
- }),
21
+ import('vue3-xml-viewer').then(module => module.default || module.XmlViewer),
40
22
  )
41
23
 
42
24
  const props = defineProps<{
@@ -44,74 +26,10 @@ const props = defineProps<{
44
26
  }>()
45
27
 
46
28
  const config = useComponentsConfig()
47
- const { t } = useTranslation()
48
29
 
49
- const xmlData = ref<string | null>(null)
50
- const loading = ref(false)
51
- const error = ref<string | null>(null)
52
- const fileTooLarge = ref(false)
53
-
54
- const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
55
-
56
- const shouldLoadXml = computed(() => {
57
- const size = fileSizeBytes.value
58
- if (!size) {
59
- // If we don't know the size, don't risk loading a potentially huge file
60
- return false
61
- }
62
-
63
- // Check if maxXmlPreviewCharSize is configured
64
- if (!config.maxXmlPreviewCharSize) {
65
- // If no limit is set, don't load unknown files
66
- return false
67
- }
68
-
69
- // Convert maxXmlPreviewCharSize from characters to bytes (rough estimate)
70
- // Assuming average 1 byte per character for XML
71
- const maxByteSize = config.maxXmlPreviewCharSize
72
-
73
- return size <= maxByteSize
74
- })
75
-
76
- const fetchXmlData = async () => {
77
- // Check if file is too large or size is unknown before making the request
78
- if (!shouldLoadXml.value) {
79
- fileTooLarge.value = true
80
- return
81
- }
82
-
83
- loading.value = true
84
- error.value = null
85
-
86
- try {
87
- const response = await fetch(props.resource.url)
88
- // const response = await fetch('/test-data.xml') // For testing locally without CORS issues
89
- if (!response.ok) {
90
- throw new Error(`HTTP error! status: ${response.status}`)
91
- }
92
- const data = await response.text()
93
-
94
- // Use the XML data as string - let the XML viewer handle large files
95
- xmlData.value = data
96
- }
97
- catch (err) {
98
- console.error('Error loading XML:', err)
99
-
100
- if (err instanceof TypeError) {
101
- error.value = 'network'
102
- }
103
- else {
104
- error.value = 'generic'
105
- }
106
-
107
- xmlData.value = null
108
- }
109
- finally {
110
- loading.value = false
111
- }
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()
112
34
  }
113
-
114
- onMounted(() => {
115
- fetchXmlData()
116
- })
117
35
  </script>
@@ -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"