@datagouv/components-next 1.0.2-dev.10 → 1.0.2-dev.100

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 (107) 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-C2j760M5.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-PFfBR4J8.js +40 -0
  7. package/dist/{Map-BjUnLyj8.js → Map-BUtPf5GN.js} +756 -756
  8. package/dist/MapContainer.client-CGrS2baS.js +101 -0
  9. package/dist/{OSM-s40W6sQ2.js → OSM-D4MTdBtk.js} +2 -2
  10. package/dist/{PdfPreview.client-BVjPxlPu.js → PdfPreview.client-DU36UBGQ.js} +822 -865
  11. package/dist/{Pmtiles.client-CRJ56yX2.js → Pmtiles.client-DuTezcn5.js} +574 -579
  12. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-ProPRqX6.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-Bcq2Ye14.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 +175 -149
  21. package/dist/components.css +1 -1
  22. package/dist/{index-BZsAZ7iw.js → index-BJ-zwAF5.js} +32886 -27183
  23. package/dist/{main-qc4CO9Kn.js → main-TqHFAOCi.js} +92255 -76685
  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-BnJTx_B7.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/DataserviceCard.vue +3 -0
  32. package/src/components/DatasetCard.vue +9 -4
  33. package/src/components/Form/Listbox.vue +101 -0
  34. package/src/components/Form/SearchableSelect.vue +2 -1
  35. package/src/components/InfiniteLoader.vue +53 -0
  36. package/src/components/ObjectCardHeader.vue +11 -4
  37. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  38. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  39. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  40. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  41. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  42. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  43. package/src/components/OpenApiViewer/openapi.ts +150 -0
  44. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  45. package/src/components/Pagination.vue +8 -5
  46. package/src/components/ReadMore.vue +1 -1
  47. package/src/components/ResourceAccordion/DataStructure.vue +11 -33
  48. package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
  49. package/src/components/ResourceAccordion/Downloads.vue +160 -0
  50. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
  51. package/src/components/ResourceAccordion/MapContainer.client.vue +5 -14
  52. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  53. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -103
  54. package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
  55. package/src/components/ResourceAccordion/Preview.vue +16 -21
  56. package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
  57. package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
  58. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  59. package/src/components/ResourceAccordion/ResourceAccordion.vue +10 -109
  60. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
  61. package/src/components/ResourceExplorer/ResourceExplorer.vue +35 -20
  62. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  63. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +56 -146
  64. package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
  65. package/src/components/ReuseCard.vue +12 -4
  66. package/src/components/Search/GlobalSearch.vue +191 -110
  67. package/src/components/Search/SearchInput.vue +5 -4
  68. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  69. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  70. package/src/components/TabularExplorer/TabularExplorer.vue +973 -0
  71. package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
  72. package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
  73. package/src/components/TabularExplorer/types.ts +83 -0
  74. package/src/composables/useHasTabularData.ts +13 -0
  75. package/src/composables/useMetrics.ts +1 -1
  76. package/src/composables/useResourceCapabilities.ts +1 -1
  77. package/src/composables/useSearchFilter.ts +118 -0
  78. package/src/composables/useStableQueryParams.ts +31 -3
  79. package/src/composables/useTabularProfile.ts +70 -0
  80. package/src/config.ts +20 -3
  81. package/src/functions/api.ts +9 -37
  82. package/src/functions/api.types.ts +1 -0
  83. package/src/functions/charts.ts +68 -0
  84. package/src/functions/datasets.ts +0 -17
  85. package/src/functions/metrics.ts +6 -4
  86. package/src/functions/resources.ts +56 -1
  87. package/src/functions/tabular.ts +60 -0
  88. package/src/functions/tabularApi.ts +138 -11
  89. package/src/main.ts +94 -7
  90. package/src/types/dataservices.ts +2 -0
  91. package/src/types/pages.ts +0 -5
  92. package/src/types/posts.ts +2 -2
  93. package/src/types/reports.ts +5 -1
  94. package/src/types/search.ts +52 -1
  95. package/src/types/site.ts +5 -3
  96. package/src/types/ui.ts +2 -0
  97. package/src/types/users.ts +2 -1
  98. package/src/types/visualizations.ts +89 -0
  99. package/assets/swagger-themes/newspaper.css +0 -1670
  100. package/dist/Datafair.client-0UYUu5yf.js +0 -35
  101. package/dist/JsonPreview.client-BrTMBWHZ.js +0 -87
  102. package/dist/MapContainer.client-CUmKyByc.js +0 -107
  103. package/dist/Swagger.client-2Yn7iF0A.js +0 -4
  104. package/dist/XmlPreview.client-DxqlVnKu.js +0 -79
  105. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  106. package/src/functions/pagination.ts +0 -9
  107. /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
@@ -1,13 +1,8 @@
1
1
  <template>
2
2
  <div>
3
- <SimpleBanner
4
- v-if="hasError"
5
- type="warning"
6
- class="flex items-center space-x-2"
7
- >
8
- <RiErrorWarningLine class="shrink-0 size-6" />
9
- <span>{{ t("L'aperçu de ce fichier n'a pas pu être chargé.") }}</span>
10
- </SimpleBanner>
3
+ <PreviewUnavailable v-if="hasError">
4
+ {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
5
+ </PreviewUnavailable>
11
6
  <PreviewLoader v-else-if="loading" />
12
7
  <div
13
8
  v-else
@@ -53,7 +48,7 @@
53
48
  >
54
49
  <BrandedButton
55
50
  color="tertiary"
56
- :icon="isSortedBy(col) && sortConfig && sortConfig.type == 'asc' ? RiArrowUpLine : RiArrowDownLine"
51
+ :icon="isSortedBy(col) && sortConfig && sortConfig.direction === 'asc' ? RiArrowUpLine : RiArrowDownLine"
57
52
  icon-right
58
53
  size="xs"
59
54
  @click="sortByField(col)"
@@ -61,7 +56,7 @@
61
56
  <!-- There is a weird bug with `sr-only`, I needed to add a relative parent to avoid full page x scrolling into the void… -->
62
57
  <span class="relative">
63
58
  {{ col }}
64
- <span class="sr-only">{{ sortConfig && sortConfig.type == 'desc' ? t("Trier par ordre croissant") : t("Trier par ordre décroissant") }}</span>
59
+ <span class="sr-only">{{ sortConfig && sortConfig.direction === 'desc' ? t("Trier par ordre croissant") : t("Trier par ordre décroissant") }}</span>
65
60
  </span>
66
61
  </BrandedButton>
67
62
  </th>
@@ -105,7 +100,7 @@
105
100
 
106
101
  <script setup lang="ts">
107
102
  import { computed, onMounted, ref } from 'vue'
108
- import { RiArrowDownLine, RiArrowUpLine, RiErrorWarningLine, RiExternalLinkFill } from '@remixicon/vue'
103
+ import { RiArrowDownLine, RiArrowUpLine, RiExternalLinkFill } from '@remixicon/vue'
109
104
  import Pagination from '../Pagination.vue'
110
105
  import { getData, type SortConfig } from '../../functions/tabularApi'
111
106
  import { useFormatDate } from '../../functions/dates'
@@ -113,7 +108,7 @@ import { trackEvent } from '../../functions/matomo'
113
108
  import type { Resource } from '../../types/resources'
114
109
  import { useComponentsConfig } from '../../config'
115
110
  import BrandedButton from '../BrandedButton.vue'
116
- import SimpleBanner from '../SimpleBanner.vue'
111
+ import PreviewUnavailable from './PreviewUnavailable.vue'
117
112
  import { useTranslation } from '../../composables/useTranslation'
118
113
  import franceSvg from './france.svg?raw'
119
114
  import PreviewLoader from './PreviewLoader.vue'
@@ -127,7 +122,7 @@ const rows = ref<Array<Record<string, unknown>>>([])
127
122
  const columns = ref<Array<string>>([])
128
123
  const loading = ref(true)
129
124
  const hasError = ref(false)
130
- const sortConfig = ref<SortConfig>(null)
125
+ const sortConfig = ref<SortConfig | null>(null)
131
126
  const rowCount = ref(0)
132
127
  const config = useComponentsConfig()
133
128
  const pageSize = computed(() => config.tabularApiPageSize || 15)
@@ -143,11 +138,11 @@ function isSortedBy(col: string) {
143
138
  /**
144
139
  * Retrieve preview necessary infos
145
140
  */
146
- async function getTableInfos(page: number, sortConfig?: SortConfig) {
141
+ async function getTableInfos(page: number, sortConfig?: SortConfig | null) {
147
142
  try {
148
143
  // Check that this function return wanted data
149
144
  const response = await getData(config, props.resource.id, page, sortConfig)
150
- if ('data' in response && response.data && response.data.length > 0) {
145
+ if ('data' in response && response.data && 0 in response.data) {
151
146
  // Update existing rows
152
147
  rows.value = response.data
153
148
  columns.value = Object.keys(response.data[0]).filter(item => item !== '__id')
@@ -177,24 +172,24 @@ function changePage(page: number) {
177
172
  * Sort by a specific column
178
173
  */
179
174
  function sortByField(col: string) {
180
- if (sortConfig.value && sortConfig.value.column == col) {
181
- if (sortConfig.value.type == 'asc') {
182
- sortConfig.value.type = 'desc'
175
+ if (sortConfig.value && sortConfig.value.column === col) {
176
+ if (sortConfig.value.direction === 'asc') {
177
+ sortConfig.value.direction = 'desc'
183
178
  }
184
179
  else {
185
- sortConfig.value.type = 'asc'
180
+ sortConfig.value.direction = 'asc'
186
181
  }
187
182
  }
188
183
  else {
189
184
  if (!sortConfig.value) {
190
185
  sortConfig.value = {
191
186
  column: col,
192
- type: 'asc',
187
+ direction: 'asc',
193
188
  }
194
189
  }
195
190
  else {
196
191
  sortConfig.value.column = col
197
- sortConfig.value.type = 'asc'
192
+ sortConfig.value.direction = 'asc'
198
193
  }
199
194
  }
200
195
  currentPage.value = 1
@@ -1,7 +1,6 @@
1
1
  <template>
2
2
  <ContentLoader
3
- :width="1124"
4
- :height="300"
3
+ viewBox="0 0 1124 300"
5
4
  :speed="2"
6
5
  primary-color="#f3f3f3"
7
6
  secondary-color="#ecebeb"
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <div class="flex flex-col items-center py-12">
3
+ <img
4
+ :src="microscopeSrc"
5
+ class="h-20 mb-3"
6
+ alt=""
7
+ >
8
+ <p class="fr-text--bold mb-1">
9
+ {{ t("Aucun aperçu disponible") }}
10
+ </p>
11
+ <p class="text-sm text-gray-medium mb-0 text-center max-w-lg">
12
+ <slot />
13
+ </p>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { useTranslation } from '../../composables/useTranslation'
19
+ import microscopeSrc from '../../../assets/illustrations/microscope.svg?url'
20
+
21
+ const { t } = useTranslation()
22
+ </script>
@@ -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
  />
@@ -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
  />
@@ -265,88 +265,10 @@
265
265
  <div
266
266
  v-if="tab.key === 'downloads'"
267
267
  >
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>
268
+ <Downloads
269
+ :resource="resource"
270
+ :dataset="dataset"
271
+ />
350
272
  </div>
351
273
  <div
352
274
  v-if="tab.key === 'swagger'"
@@ -357,7 +279,7 @@
357
279
  <p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
358
280
  <p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
359
281
  </div>
360
- <Swagger
282
+ <OpenApiViewer
361
283
  v-if="hasTabularData"
362
284
  :url="`${config.tabularApiUrl}/api/resources/${props.resource.id}/swagger/`"
363
285
  />
@@ -387,9 +309,8 @@ import { trackEvent } from '../../functions/matomo'
387
309
  import CopyButton from '../CopyButton.vue'
388
310
  import { useComponentsConfig } from '../../config'
389
311
  import { getOwnerName } from '../../functions/owned'
390
- import { getResourceFormatIcon, getResourceTitleId, detectOgcService } from '../../functions/resources'
312
+ import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize } from '../../functions/resources'
391
313
  import BrandedButton from '../BrandedButton.vue'
392
- import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
393
314
  import { useTranslation } from '../../composables/useTranslation'
394
315
  import { useHasTabularData } from '../../composables/useHasTabularData'
395
316
  import Metadata from './Metadata.vue'
@@ -397,11 +318,11 @@ import SchemaBadge from './SchemaBadge.vue'
397
318
  import ResourceIcon from './ResourceIcon.vue'
398
319
  import EditButton from './EditButton.vue'
399
320
  import DataStructure from './DataStructure.vue'
321
+ import Downloads from './Downloads.vue'
400
322
  import Preview from './Preview.vue'
401
323
  import { isOrganizationCertified } from '../../functions/organizations'
402
- import SwaggerClient from './Swagger.client.vue'
324
+ import OpenApiViewer from '../OpenApiViewer/OpenApiViewer.vue'
403
325
 
404
- const GENERATED_FORMATS = ['parquet', 'pmtiles', 'geojson']
405
326
  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
327
 
407
328
  const props = withDefaults(defineProps<{
@@ -418,7 +339,6 @@ const props = withDefaults(defineProps<{
418
339
 
419
340
  const config = useComponentsConfig()
420
341
 
421
- const Swagger = defineAsyncComponent(() => import('./Swagger.client.vue'))
422
342
  const MapContainer = defineAsyncComponent(() => import('./MapContainer.client.vue'))
423
343
  const Pmtiles = defineAsyncComponent(() => import('./Pmtiles.client.vue'))
424
344
  const JsonPreview = defineAsyncComponent(() => import('./JsonPreview.client.vue'))
@@ -462,24 +382,6 @@ const ogcService = computed(() => detectOgcService(props.resource))
462
382
 
463
383
  const ogcWms = computed(() => ogcService.value === 'wms')
464
384
 
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
385
  const open = ref(props.expandedOnMount)
484
386
  const toggle = () => {
485
387
  open.value = !open.value
@@ -542,7 +444,6 @@ const communityResource = computed<CommunityResource | null>(() => {
542
444
  const owner = computed(() => communityResource.value ? getOwnerName(communityResource.value) : null)
543
445
 
544
446
  const lastUpdate = props.resource.last_modified
545
- const conversionsLastUpdate = computed(() => formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined))
546
447
  const availabilityChecked = props.resource.extras && 'check:available' in props.resource.extras
547
448
  const resourceFilesize = computed(() => getResourceFilesize(props.resource))
548
449
 
@@ -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>
@@ -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
@@ -159,6 +163,22 @@ watch(searchDebounced, () => {
159
163
  }
160
164
  })
161
165
 
166
+ // Separate useFetch for loadMore, initialized at setup time with immediate: false
167
+ // so that it doesn't fetch until execute() is called from the event handler.
168
+ const loadMoreType = ref<ResourceType>('main')
169
+ const loadMorePage = ref(1)
170
+ const loadMoreParams = computed(() => ({
171
+ type: loadMoreType.value,
172
+ page_size: PAGE_SIZE,
173
+ page: loadMorePage.value,
174
+ q: searchDebounced.value || undefined,
175
+ }))
176
+ const { data: loadMoreData, execute: executeLoadMore } = await useFetch<PaginatedArray<Resource>>(url, {
177
+ params: loadMoreParams,
178
+ immediate: false,
179
+ watch: false,
180
+ })
181
+
162
182
  const loadMore = async (type: ResourceType) => {
163
183
  const index = RESOURCE_TYPE.indexOf(type)
164
184
  if (index === -1) return
@@ -166,17 +186,12 @@ const loadMore = async (type: ResourceType) => {
166
186
  const extraRef = extraResourcesByType[index]!
167
187
  pageRef.value++
168
188
 
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
- })
189
+ loadMoreType.value = type
190
+ loadMorePage.value = pageRef.value
191
+ await executeLoadMore()
177
192
 
178
- if (data.value) {
179
- extraRef.value = [...extraRef.value, ...data.value.data]
193
+ if (loadMoreData.value) {
194
+ extraRef.value = [...extraRef.value, ...loadMoreData.value.data]
180
195
  }
181
196
  }
182
197
 
@@ -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"