@datagouv/components-next 1.0.2-dev.7 → 1.0.2-dev.71

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 (83) hide show
  1. package/assets/main.css +4 -0
  2. package/dist/Datafair.client-BzW-ctDf.js +30 -0
  3. package/dist/JsonPreview.client-BfMSzR07.js +40 -0
  4. package/dist/{MapContainer.client-DRkAmdOc.js → MapContainer.client-CLs-im9i.js} +35 -38
  5. package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-C13PQCU_.js} +822 -865
  6. package/dist/{Pmtiles.client-BR7_ldHY.js → Pmtiles.client-CL7PXXDl.js} +574 -579
  7. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-C6XnsZ-7.js +61 -0
  8. package/dist/XmlPreview.client-KaENrbbG.js +34 -0
  9. package/dist/components-next.css +3 -3
  10. package/dist/components-next.js +166 -148
  11. package/dist/components.css +1 -1
  12. package/dist/{index-SrYZwgCT.js → index-C7WVVGgD.js} +1 -1
  13. package/dist/{main-B2kXxWRG.js → main-K-42Oe8-.js} +91315 -75834
  14. package/dist/{vue3-xml-viewer.common-BRxsqI9j.js → vue3-xml-viewer.common-sHPSE-jD.js} +1 -1
  15. package/package.json +16 -10
  16. package/src/components/ActivityList/ActivityList.vue +0 -2
  17. package/src/components/Chart/ChartViewer.vue +226 -0
  18. package/src/components/Chart/ChartViewerWrapper.vue +170 -0
  19. package/src/components/Form/Listbox.vue +101 -0
  20. package/src/components/Form/SearchableSelect.vue +2 -1
  21. package/src/components/InfiniteLoader.vue +53 -0
  22. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  23. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  24. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  25. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  26. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  27. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  28. package/src/components/OpenApiViewer/openapi.ts +150 -0
  29. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  30. package/src/components/Pagination.vue +8 -5
  31. package/src/components/ReadMore.vue +1 -1
  32. package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
  33. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
  34. package/src/components/ResourceAccordion/MapContainer.client.vue +7 -11
  35. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  36. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -103
  37. package/src/components/ResourceAccordion/Pmtiles.client.vue +5 -10
  38. package/src/components/ResourceAccordion/Preview.vue +16 -21
  39. package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
  40. package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
  41. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  42. package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -7
  43. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
  44. package/src/components/ResourceExplorer/ResourceExplorer.vue +81 -13
  45. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  46. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +30 -11
  47. package/src/components/Search/GlobalSearch.vue +173 -108
  48. package/src/components/Search/SearchInput.vue +3 -3
  49. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  50. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  51. package/src/components/TabularExplorer/TabularExplorer.vue +870 -0
  52. package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
  53. package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
  54. package/src/components/TabularExplorer/types.ts +83 -0
  55. package/src/composables/useHasTabularData.ts +6 -0
  56. package/src/composables/useResourceCapabilities.ts +1 -1
  57. package/src/composables/useSearchFilter.ts +118 -0
  58. package/src/composables/useStableQueryParams.ts +31 -3
  59. package/src/config.ts +3 -0
  60. package/src/functions/api.ts +34 -33
  61. package/src/functions/api.types.ts +1 -0
  62. package/src/functions/charts.ts +68 -0
  63. package/src/functions/datasets.ts +0 -17
  64. package/src/functions/resources.ts +56 -1
  65. package/src/functions/tabular.ts +60 -0
  66. package/src/functions/tabularApi.ts +138 -11
  67. package/src/main.ts +55 -7
  68. package/src/types/dataservices.ts +2 -0
  69. package/src/types/pages.ts +0 -5
  70. package/src/types/posts.ts +2 -2
  71. package/src/types/reports.ts +3 -0
  72. package/src/types/search.ts +52 -1
  73. package/src/types/site.ts +5 -3
  74. package/src/types/users.ts +0 -1
  75. package/src/types/visualizations.ts +89 -0
  76. package/assets/swagger-themes/newspaper.css +0 -1670
  77. package/dist/Datafair.client-E5D6ePRC.js +0 -35
  78. package/dist/JsonPreview.client-C-6eBbPw.js +0 -87
  79. package/dist/Swagger.client-D4-F6yEf.js +0 -4
  80. package/dist/XmlPreview.client-Dl2VCgXF.js +0 -79
  81. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  82. package/src/functions/pagination.ts +0 -9
  83. /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
@@ -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