@datagouv/components-next 1.0.2-dev.17 → 1.0.2-dev.170
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.
- package/README.md +7 -6
- package/assets/main.css +4 -0
- package/dist/{Control-DuZJdKV_.js → Control-B9WQxlpJ.js} +1 -1
- package/dist/{Datafair.client-Ds3VQrRi.js → Datafair.client-B-OG5WtK.js} +4 -4
- package/dist/{Event--kp8kMdJ.js → Event-iXDrp-fy.js} +31 -31
- package/dist/{Image-34hvypZI.js → Image-mNMBOofs.js} +9 -9
- package/dist/ImagePreview.client-D0c-ifUi.js +36 -0
- package/dist/JsonPreview.client-Dwh9sW9M.js +40 -0
- package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
- package/dist/{MapContainer.client-B2BijMx5.js → MapContainer.client-C2AZ7sOB.js} +38 -39
- package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
- package/dist/{PdfPreview.client-DJBMzrlC.js → PdfPreview.client-8NzNfW8b.js} +823 -855
- package/dist/Pmtiles.client-BFH4rTB7.js +26324 -0
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BOgE441d.js +58 -0
- package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-VGQ7nkMS.js} +2 -2
- package/dist/{Tile-DbNFNPfU.js → Tile-CSpn0Ca8.js} +160 -163
- package/dist/{TileImage-BsXBxMtq.js → TileImage-CpbaEFwd.js} +143 -131
- package/dist/{View-BR92hTWP.js → View-BYBpt-AU.js} +483 -422
- package/dist/XmlPreview.client-DMOUO508.js +34 -0
- package/dist/{common-PJfpC179.js → common-BoeTUnDZ.js} +52 -52
- package/dist/components-next.css +1 -52
- package/dist/components-next.js +183 -146
- package/dist/components.css +2 -2
- package/dist/{index-BFQX7Ccz.js → index-C9obT9DJ.js} +21834 -15810
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-B4AZU0vr.js} +185 -187
- package/dist/{main-vqhkVhVS.js → main-DNoYWfit.js} +40723 -73240
- package/dist/{proj-DsetBcW7.js → proj-DxGbobp7.js} +662 -626
- package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
- package/dist/{tilecoord-Db24Px13.js → tilecoord-DvX7WWiA.js} +273 -264
- package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
- package/dist/{vue3-xml-viewer.common-BcNJo35o.js → vue3-xml-viewer.common-Cp7Cu5ME.js} +16 -16
- package/package.json +47 -38
- package/src/chart.ts +5 -0
- package/src/components/ActivityList/ActivityList.vue +3 -2
- package/src/components/Chart/ChartViewer.vue +320 -0
- package/src/components/Chart/ChartViewerWrapper.vue +182 -0
- package/src/components/DataserviceCard.vue +9 -0
- package/src/components/DataserviceQuality.vue +82 -0
- package/src/components/DataserviceQualityInline.vue +31 -0
- package/src/components/DataserviceQualityTooltipContent.vue +79 -0
- package/src/components/DatasetCard.vue +9 -4
- package/src/components/DatasetQuality.vue +11 -11
- package/src/components/DatasetQualityInline.vue +2 -2
- package/src/components/DatasetQualityTooltipContent.vue +1 -1
- package/src/components/Form/Listbox.vue +101 -0
- package/src/components/InfiniteLoader.vue +53 -0
- package/src/components/ObjectCardHeader.vue +11 -4
- package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
- package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
- package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
- package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
- package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
- package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
- package/src/components/OpenApiViewer/openapi.ts +150 -0
- package/src/components/OrganizationNameWithCertificate.vue +3 -2
- package/src/components/Pagination.vue +8 -5
- package/src/components/RadioInput.vue +7 -2
- package/src/components/ResourceAccordion/DataStructure.vue +11 -33
- package/src/components/ResourceAccordion/Downloads.vue +160 -0
- package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
- package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
- package/src/components/ResourceAccordion/MapContainer.client.vue +1 -3
- package/src/components/ResourceAccordion/Metadata.vue +1 -2
- package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -87
- package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -1
- package/src/components/ResourceAccordion/Preview.vue +11 -11
- package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +19 -113
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
- package/src/components/ResourceExplorer/ResourceExplorer.vue +22 -19
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +64 -148
- package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
- package/src/components/ReuseCard.vue +14 -6
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/ReuseHorizontalCard.vue +2 -2
- package/src/components/Search/GlobalSearch.vue +219 -117
- package/src/components/Search/SearchInput.vue +5 -4
- package/src/components/TabularExplorer/TabularCell.vue +52 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +1225 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +370 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +107 -0
- package/src/components/TabularExplorer/types.ts +86 -0
- package/src/composables/useHasTabularData.ts +15 -1
- package/src/composables/useMetrics.ts +1 -1
- package/src/composables/useResourceCapabilities.ts +28 -15
- package/src/composables/useSearchFilter.ts +120 -0
- package/src/composables/useStableQueryParams.ts +38 -6
- package/src/composables/useTabularProfile.ts +70 -0
- package/src/config.ts +21 -3
- package/src/functions/activities.ts +3 -3
- package/src/functions/api.ts +20 -42
- package/src/functions/api.types.ts +1 -0
- package/src/functions/charts.ts +99 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/dates.ts +3 -2
- package/src/functions/metrics.ts +6 -4
- package/src/functions/resources.ts +117 -1
- package/src/functions/tabular.ts +214 -0
- package/src/functions/tabularApi.ts +96 -11
- package/src/main.ts +109 -14
- package/src/types/dataservices.ts +17 -0
- package/src/types/organizations.ts +10 -0
- package/src/types/pages.ts +0 -5
- package/src/types/posts.ts +2 -2
- package/src/types/reports.ts +5 -1
- package/src/types/resources.ts +0 -1
- package/src/types/reuses.ts +12 -1
- package/src/types/search.ts +63 -1
- package/src/types/site.ts +5 -3
- package/src/types/ui.ts +2 -0
- package/src/types/users.ts +2 -1
- package/src/types/visualizations.ts +93 -0
- package/assets/swagger-themes/newspaper.css +0 -1670
- package/dist/JsonPreview.client-CYxmz0yV.js +0 -72
- package/dist/Pmtiles.client-C_hvRDuy.js +0 -25185
- package/dist/Swagger.client-B4y0hXME.js +0 -4
- package/dist/XmlPreview.client-LETEfNBH.js +0 -64
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
- /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
- /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
- /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
|
-
<
|
|
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
|
-
<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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 === '
|
|
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
|
-
<
|
|
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
|
|
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
|
|
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: '
|
|
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
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 {
|
|
14
|
+
import { defineAsyncComponent } from 'vue'
|
|
29
15
|
import { useComponentsConfig } from '../../config'
|
|
30
|
-
import
|
|
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(
|
|
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
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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"
|