@datagouv/components-next 1.0.2-dev.11 → 1.0.2-dev.111
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/assets/main.css +4 -0
- package/dist/{Control-DuZJdKV_.js → Control-ZFh5ta_U.js} +1 -1
- package/dist/{Datafair.client-8haHXl47.js → Datafair.client-CKB2P_X1.js} +1 -1
- package/dist/{Event--kp8kMdJ.js → Event-DSQcW7OF.js} +24 -24
- package/dist/{Image-34hvypZI.js → Image-BijNEG0p.js} +6 -6
- package/dist/JsonPreview.client-Bx11-jfT.js +40 -0
- package/dist/{Map-BjUnLyj8.js → Map-BUtPf5GN.js} +756 -756
- package/dist/{MapContainer.client-l6HuXTHR.js → MapContainer.client-CdZSeT_L.js} +37 -38
- package/dist/{OSM-s40W6sQ2.js → OSM-D4MTdBtk.js} +2 -2
- package/dist/{PdfPreview.client-4OueK-2Z.js → PdfPreview.client-Bh9lP-qU.js} +822 -850
- package/dist/{Pmtiles.client-4j3VTYkz.js → Pmtiles.client-Bi46wN14.js} +1 -1
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BnC7vWGP.js +61 -0
- package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-hJQIqcZm.js} +2 -2
- package/dist/{Tile-DbNFNPfU.js → Tile-Dcl7oIVu.js} +35 -35
- package/dist/{TileImage-BsXBxMtq.js → TileImage-BJeHipMX.js} +4 -4
- package/dist/{View-BR92hTWP.js → View-xp_P_OHw.js} +412 -401
- package/dist/XmlPreview.client-oFAOv828.js +34 -0
- package/dist/{common-PJfpC179.js → common-BjQlan3k.js} +36 -36
- package/dist/components-next.css +6 -6
- package/dist/components-next.js +165 -142
- package/dist/components.css +1 -1
- package/dist/{index-CVTIoZQ0.js → index-CxCuKQ81.js} +32886 -27183
- package/dist/main-CQ9ZQG7n.js +73607 -0
- package/dist/{proj-DsetBcW7.js → proj-CsNo9yH1.js} +532 -512
- package/dist/{tilecoord-Db24Px13.js → tilecoord-A0fLnBZr.js} +28 -28
- package/dist/{vue3-xml-viewer.common-CWer_T5-.js → vue3-xml-viewer.common-B9qp90K_.js} +1 -1
- package/package.json +25 -11
- package/src/chart.ts +5 -0
- package/src/components/ActivityList/ActivityList.vue +3 -2
- package/src/components/Chart/ChartViewer.vue +226 -0
- package/src/components/Chart/ChartViewerWrapper.vue +170 -0
- package/src/components/DataserviceCard.vue +3 -0
- package/src/components/DatasetCard.vue +9 -4
- package/src/components/Form/Listbox.vue +101 -0
- package/src/components/Form/SearchableSelect.vue +2 -1
- 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/ReadMore.vue +1 -1
- package/src/components/ResourceAccordion/DataStructure.vue +11 -33
- package/src/components/ResourceAccordion/Downloads.vue +160 -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/Preview.vue +11 -11
- package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +11 -110
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
- package/src/components/ResourceExplorer/ResourceExplorer.vue +14 -10
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +50 -148
- package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
- package/src/components/ReuseCard.vue +12 -4
- package/src/components/Search/GlobalSearch.vue +201 -113
- package/src/components/Search/SearchInput.vue +5 -4
- package/src/components/TabularExplorer/TabularCell.vue +51 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +973 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
- package/src/components/TabularExplorer/types.ts +83 -0
- package/src/composables/useHasTabularData.ts +13 -0
- package/src/composables/useMetrics.ts +1 -1
- package/src/composables/useResourceCapabilities.ts +1 -1
- package/src/composables/useSearchFilter.ts +118 -0
- package/src/composables/useStableQueryParams.ts +38 -6
- package/src/composables/useTabularProfile.ts +70 -0
- package/src/config.ts +20 -3
- package/src/functions/activities.ts +3 -3
- package/src/functions/api.ts +9 -37
- package/src/functions/api.types.ts +1 -0
- package/src/functions/charts.ts +68 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/metrics.ts +6 -4
- package/src/functions/resources.ts +56 -1
- package/src/functions/tabular.ts +60 -0
- package/src/functions/tabularApi.ts +138 -11
- package/src/main.ts +90 -9
- package/src/types/dataservices.ts +2 -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/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 +89 -0
- package/assets/swagger-themes/newspaper.css +0 -1670
- package/dist/JsonPreview.client-D53pj9Cw.js +0 -72
- package/dist/Swagger.client-DPBmsH9q.js +0 -4
- package/dist/XmlPreview.client-XElkoA4F.js +0 -64
- package/dist/main-BbT-LUXy.js +0 -105854
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
|
@@ -49,45 +49,23 @@
|
|
|
49
49
|
</template>
|
|
50
50
|
|
|
51
51
|
<script setup lang="ts">
|
|
52
|
-
import {
|
|
52
|
+
import { computed } from 'vue'
|
|
53
53
|
import type { Resource } from '../../types/resources'
|
|
54
|
-
import { useGetProfile } from '../../functions/tabularApi'
|
|
55
54
|
import { useTranslation } from '../../composables/useTranslation'
|
|
55
|
+
import { injectTabularProfile } from '../../composables/useTabularProfile'
|
|
56
56
|
import PreviewLoader from './PreviewLoader.vue'
|
|
57
57
|
|
|
58
58
|
const props = defineProps<{ resource: Resource }>()
|
|
59
|
-
const getProfile = useGetProfile()
|
|
60
59
|
const { t } = useTranslation()
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
61
|
+
// Profile is shared with sibling components (e.g. TabularExplorer) via
|
|
62
|
+
// `provideTabularProfile` in the parent. Falls back to a local fetch
|
|
63
|
+
// when no parent provides it (standalone usage).
|
|
64
|
+
const { data: profileData, status } = await injectTabularProfile(() => props.resource.id)
|
|
67
65
|
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
onMounted(async () => {
|
|
75
|
-
try {
|
|
76
|
-
const response = await getProfile(props.resource.id) // Assurez-vous que cette fonction retourne bien les données attendues
|
|
77
|
-
if ('profile' in response && response.profile) {
|
|
78
|
-
columns.value = Object.keys(response.profile.columns)
|
|
79
|
-
columnsInfo.value = response.profile.columns
|
|
80
|
-
hasColumnInfo.value = true
|
|
81
|
-
loading.value = false
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
hasError.value = true
|
|
85
|
-
loading.value = false
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
hasError.value = true
|
|
90
|
-
loading.value = false
|
|
91
|
-
}
|
|
92
|
-
})
|
|
66
|
+
const loading = computed(() => status.value === 'idle' || status.value === 'pending')
|
|
67
|
+
const hasError = computed(() => status.value === 'error')
|
|
68
|
+
const hasColumnInfo = computed(() => !!profileData.value?.profile?.columns)
|
|
69
|
+
const columns = computed(() => profileData.value?.profile ? Object.keys(profileData.value.profile.columns) : [])
|
|
70
|
+
const columnsInfo = computed(() => profileData.value?.profile?.columns ?? {})
|
|
93
71
|
</script>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<dl class="fr-pl-0">
|
|
3
|
+
<dt
|
|
4
|
+
v-if="resource.format === 'url'"
|
|
5
|
+
class="font-bold fr-text--sm fr-mb-0"
|
|
6
|
+
>
|
|
7
|
+
{{ t("URL d'origine") }}
|
|
8
|
+
</dt>
|
|
9
|
+
<dt
|
|
10
|
+
v-else
|
|
11
|
+
class="font-bold fr-text--sm fr-mb-0"
|
|
12
|
+
>
|
|
13
|
+
{{ t('Format original') }}
|
|
14
|
+
</dt>
|
|
15
|
+
<dd class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center">
|
|
16
|
+
<span
|
|
17
|
+
v-if="resource.format === 'url'"
|
|
18
|
+
class="inline-flex items-center max-w-full"
|
|
19
|
+
>
|
|
20
|
+
<a
|
|
21
|
+
:href="resource.latest"
|
|
22
|
+
class="fr-link no-icon-after truncate"
|
|
23
|
+
rel="ugc nofollow noopener"
|
|
24
|
+
target="_blank"
|
|
25
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
|
|
26
|
+
>
|
|
27
|
+
{{ resource.url }}
|
|
28
|
+
</a>
|
|
29
|
+
<span class="fr-ml-1v fr-icon-external-link-line fr-icon--sm shrink-0" />
|
|
30
|
+
</span>
|
|
31
|
+
<span v-else>
|
|
32
|
+
<span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
|
|
33
|
+
<a
|
|
34
|
+
:href="resource.latest"
|
|
35
|
+
class="fr-link"
|
|
36
|
+
rel="ugc nofollow noopener"
|
|
37
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${resource.format}`)"
|
|
38
|
+
>
|
|
39
|
+
<span>{{ t('Format {format}', { format: resource.format }) }}<span v-if="resourceFilesize"> - {{ filesize(resourceFilesize) }}</span></span>
|
|
40
|
+
</a>
|
|
41
|
+
</span>
|
|
42
|
+
<CopyButton
|
|
43
|
+
:label="t('Copier le lien')"
|
|
44
|
+
:copied-label="t('Lien copié !')"
|
|
45
|
+
:text="resource.latest"
|
|
46
|
+
class="relative"
|
|
47
|
+
/>
|
|
48
|
+
</dd>
|
|
49
|
+
<template v-if="generatedFormats.length">
|
|
50
|
+
<dt class="font-bold fr-text--sm fr-mb-0">
|
|
51
|
+
{{ t('Formats générés automatiquement par {platform} (dernière mise à jour {date})', { platform: config.name, date: conversionsLastUpdate }) }}
|
|
52
|
+
</dt>
|
|
53
|
+
<dd
|
|
54
|
+
v-for="generatedFormat in generatedFormats"
|
|
55
|
+
:key="generatedFormat.format"
|
|
56
|
+
class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
|
|
57
|
+
>
|
|
58
|
+
<span>
|
|
59
|
+
<span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
|
|
60
|
+
<a
|
|
61
|
+
:href="generatedFormat.url"
|
|
62
|
+
class="fr-link"
|
|
63
|
+
rel="ugc nofollow noopener"
|
|
64
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${generatedFormat.format}`)"
|
|
65
|
+
>
|
|
66
|
+
<span>{{ t('Format {format}', { format: generatedFormat.format }) }}<span v-if="generatedFormat.size"> - {{ filesize(generatedFormat.size) }}</span></span>
|
|
67
|
+
</a>
|
|
68
|
+
</span>
|
|
69
|
+
<CopyButton
|
|
70
|
+
:label="t('Copier le lien')"
|
|
71
|
+
:copied-label="t('Lien copié !')"
|
|
72
|
+
:text="generatedFormat.url"
|
|
73
|
+
class="relative"
|
|
74
|
+
/>
|
|
75
|
+
</dd>
|
|
76
|
+
</template>
|
|
77
|
+
<template v-if="wfsFormats.length">
|
|
78
|
+
<dt class="font-bold fr-text--sm fr-mb-0">
|
|
79
|
+
<div class="flex gap-1 items-center">
|
|
80
|
+
{{ t('Formats exportés depuis le service WFS') }}
|
|
81
|
+
<span v-if="defaultWfsProjection"> ({{ t('projection {crs}', { crs: defaultWfsProjection }) }})</span>
|
|
82
|
+
<Tooltip>
|
|
83
|
+
<RiInformationLine
|
|
84
|
+
class="flex-none size-4"
|
|
85
|
+
:aria-label="t(`Le lien de téléchargement interroge directement le flux WFS distant. Le nombre de features téléchargées peut être limité.`)"
|
|
86
|
+
aria-hidden="true"
|
|
87
|
+
/>
|
|
88
|
+
<template #tooltip>
|
|
89
|
+
<p class="text-sm font-normal mb-0">
|
|
90
|
+
{{ t(`Le lien de téléchargement interroge directement le flux WFS distant.`) }}
|
|
91
|
+
</p>
|
|
92
|
+
<p class="text-sm font-normal mb-0">
|
|
93
|
+
{{ t(`Le nombre de features téléchargées peut être limité.`) }}
|
|
94
|
+
</p>
|
|
95
|
+
</template>
|
|
96
|
+
</Tooltip>
|
|
97
|
+
</div>
|
|
98
|
+
</dt>
|
|
99
|
+
<dd
|
|
100
|
+
v-for="wfsFormat in wfsFormats"
|
|
101
|
+
:key="wfsFormat.format"
|
|
102
|
+
class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
|
|
103
|
+
>
|
|
104
|
+
<span>
|
|
105
|
+
<span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
|
|
106
|
+
<a
|
|
107
|
+
:href="wfsFormat.url"
|
|
108
|
+
class="fr-link"
|
|
109
|
+
rel="ugc nofollow noopener"
|
|
110
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${wfsFormat.format}`)"
|
|
111
|
+
>
|
|
112
|
+
<span>{{ t('Format {format}', { format: wfsFormat.format }) }}</span>
|
|
113
|
+
</a>
|
|
114
|
+
</span>
|
|
115
|
+
<CopyButton
|
|
116
|
+
:label="t('Copier le lien')"
|
|
117
|
+
:copied-label="t('Lien copié !')"
|
|
118
|
+
:text="wfsFormat.url"
|
|
119
|
+
class="relative"
|
|
120
|
+
/>
|
|
121
|
+
</dd>
|
|
122
|
+
</template>
|
|
123
|
+
</dl>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<script setup lang="ts">
|
|
127
|
+
import { computed } from 'vue'
|
|
128
|
+
import { RiInformationLine } from '@remixicon/vue'
|
|
129
|
+
import CopyButton from '../CopyButton.vue'
|
|
130
|
+
import Tooltip from '../Tooltip.vue'
|
|
131
|
+
import { filesize } from '../../functions/helpers'
|
|
132
|
+
import { getResourceFilesize } from '../../functions/resources'
|
|
133
|
+
import { trackEvent } from '../../functions/matomo'
|
|
134
|
+
import { useComponentsConfig } from '../../config'
|
|
135
|
+
import { useFormatDate } from '../../functions/dates'
|
|
136
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
137
|
+
import { useResourceCapabilities } from '../../composables/useResourceCapabilities'
|
|
138
|
+
import type { Resource } from '../../types/resources'
|
|
139
|
+
import type { Dataset, DatasetV2 } from '../../types/datasets'
|
|
140
|
+
|
|
141
|
+
const props = defineProps<{
|
|
142
|
+
resource: Resource
|
|
143
|
+
dataset: Dataset | DatasetV2
|
|
144
|
+
}>()
|
|
145
|
+
|
|
146
|
+
const { t } = useTranslation()
|
|
147
|
+
const config = useComponentsConfig()
|
|
148
|
+
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
149
|
+
|
|
150
|
+
const { generatedFormats, wfsFormats, defaultWfsProjection } = useResourceCapabilities(
|
|
151
|
+
() => props.resource,
|
|
152
|
+
() => props.dataset,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const resourceFilesize = computed(() => getResourceFilesize(props.resource))
|
|
156
|
+
|
|
157
|
+
const conversionsLastUpdate = computed(() =>
|
|
158
|
+
formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined),
|
|
159
|
+
)
|
|
160
|
+
</script>
|
|
@@ -1,48 +1,31 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
</div>
|
|
20
|
-
<PreviewUnavailable v-else-if="fileTooLarge">
|
|
21
|
-
{{ fileSizeBytes
|
|
22
|
-
? t("Le fichier JSON est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.")
|
|
23
|
-
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
|
|
24
|
-
}}
|
|
25
|
-
</PreviewUnavailable>
|
|
26
|
-
<PreviewUnavailable v-else-if="error === 'network'">
|
|
27
|
-
{{ 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.") }}
|
|
28
|
-
</PreviewUnavailable>
|
|
29
|
-
<PreviewUnavailable v-else-if="error">
|
|
30
|
-
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
|
|
31
|
-
</PreviewUnavailable>
|
|
32
|
-
</div>
|
|
2
|
+
<PreviewWrapper
|
|
3
|
+
v-slot="{ data }"
|
|
4
|
+
file-type="JSON"
|
|
5
|
+
:resource="resource"
|
|
6
|
+
:max-size="config.maxJsonPreviewCharSize"
|
|
7
|
+
:load="load"
|
|
8
|
+
>
|
|
9
|
+
<JsonViewer
|
|
10
|
+
:value="data"
|
|
11
|
+
boxed
|
|
12
|
+
sort
|
|
13
|
+
theme="light"
|
|
14
|
+
:max-depth="3"
|
|
15
|
+
:expand-depth="2"
|
|
16
|
+
:indent-width="2"
|
|
17
|
+
/>
|
|
18
|
+
</PreviewWrapper>
|
|
33
19
|
</template>
|
|
34
20
|
|
|
35
21
|
<script setup lang="ts">
|
|
36
|
-
import {
|
|
22
|
+
import { defineAsyncComponent } from 'vue'
|
|
37
23
|
import { useComponentsConfig } from '../../config'
|
|
38
|
-
import
|
|
24
|
+
import PreviewWrapper from './PreviewWrapper.vue'
|
|
39
25
|
import type { Resource } from '../../types/resources'
|
|
40
|
-
import { useTranslation } from '../../composables/useTranslation'
|
|
41
|
-
import { getResourceFilesize } from '../../functions/datasets'
|
|
42
26
|
|
|
43
27
|
const JsonViewer = defineAsyncComponent(() =>
|
|
44
28
|
import('vue3-json-viewer').then((module) => {
|
|
45
|
-
// Import CSS when component loads
|
|
46
29
|
import('vue3-json-viewer/dist/vue3-json-viewer.css')
|
|
47
30
|
return module.JsonViewer
|
|
48
31
|
}),
|
|
@@ -53,74 +36,10 @@ const props = defineProps<{
|
|
|
53
36
|
}>()
|
|
54
37
|
|
|
55
38
|
const config = useComponentsConfig()
|
|
56
|
-
const { t } = useTranslation()
|
|
57
39
|
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
|
|
64
|
-
|
|
65
|
-
const shouldLoadJson = computed(() => {
|
|
66
|
-
const size = fileSizeBytes.value
|
|
67
|
-
if (!size) {
|
|
68
|
-
// If we don't know the size, don't risk loading a potentially huge file
|
|
69
|
-
return false
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Check if maxJsonPreviewCharSize is configured
|
|
73
|
-
if (!config.maxJsonPreviewCharSize) {
|
|
74
|
-
// If no limit is set, don't load unknown files
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Convert maxJsonPreviewCharSize from characters to bytes (rough estimate)
|
|
79
|
-
// Assuming average 1 byte per character for JSON
|
|
80
|
-
const maxByteSize = config.maxJsonPreviewCharSize
|
|
81
|
-
|
|
82
|
-
return size <= maxByteSize
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
const fetchJsonData = async () => {
|
|
86
|
-
// Check if file is too large or size is unknown before making the request
|
|
87
|
-
if (!shouldLoadJson.value) {
|
|
88
|
-
fileTooLarge.value = true
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
loading.value = true
|
|
93
|
-
error.value = null
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const response = await fetch(props.resource.url)
|
|
97
|
-
// const response = await fetch('/test-data.json') // For testing locally without CORS issues
|
|
98
|
-
if (!response.ok) {
|
|
99
|
-
throw new Error(`HTTP error! status: ${response.status}`)
|
|
100
|
-
}
|
|
101
|
-
const data = await response.json()
|
|
102
|
-
|
|
103
|
-
// Use the original data directly - let the JSON viewer handle large files
|
|
104
|
-
jsonData.value = data
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
console.error('Error loading JSON:', err)
|
|
108
|
-
|
|
109
|
-
if (err instanceof TypeError) {
|
|
110
|
-
error.value = 'network'
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
error.value = 'generic'
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
jsonData.value = null
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
119
|
-
loading.value = false
|
|
120
|
-
}
|
|
40
|
+
const load = async () => {
|
|
41
|
+
const response = await fetch(props.resource.url)
|
|
42
|
+
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
|
43
|
+
return response.json()
|
|
121
44
|
}
|
|
122
|
-
|
|
123
|
-
onMounted(() => {
|
|
124
|
-
fetchJsonData()
|
|
125
|
-
})
|
|
126
45
|
</script>
|
|
@@ -89,10 +89,8 @@ async function displayMap() {
|
|
|
89
89
|
|
|
90
90
|
const attributions = new GeoportalAttribution({
|
|
91
91
|
position: 'bottom-right',
|
|
92
|
-
|
|
93
|
-
// see https://github.com/IGNF/geopf-extensions-openlayers/issues/497
|
|
92
|
+
collapsed: false,
|
|
94
93
|
})
|
|
95
|
-
attributions.setCollapsed(false)
|
|
96
94
|
map.addControl(attributions)
|
|
97
95
|
|
|
98
96
|
const layerImport = new LayerImport({
|
|
@@ -7,9 +7,8 @@ import DescriptionTerm from '../DescriptionTerm.vue'
|
|
|
7
7
|
import { useFormatDate } from '../../functions/dates'
|
|
8
8
|
import { filesize } from '../../functions/helpers'
|
|
9
9
|
import ExtraAccordion from '../ExtraAccordion.vue'
|
|
10
|
-
import { getResourceTitleId, getResourceLabel } from '../../functions/resources'
|
|
10
|
+
import { getResourceTitleId, getResourceLabel, getResourceFilesize } from '../../functions/resources'
|
|
11
11
|
import { useTranslation } from '../../composables/useTranslation'
|
|
12
|
-
import { getResourceFilesize } from '../../functions/datasets'
|
|
13
12
|
|
|
14
13
|
const props = defineProps<{
|
|
15
14
|
resource: Resource
|
|
@@ -1,48 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<PreviewWrapper
|
|
3
|
+
v-slot="{ data }"
|
|
4
|
+
file-type="PDF"
|
|
5
|
+
:resource="resource"
|
|
6
|
+
:max-size="config.maxPdfPreviewByteSize"
|
|
7
|
+
:load="load"
|
|
8
|
+
@loaded="renderAllPages"
|
|
9
|
+
>
|
|
3
10
|
<div
|
|
4
|
-
v-if="pdfReady"
|
|
5
11
|
ref="containerRef"
|
|
6
12
|
class="w-full overflow-y-auto max-h-[80vh] space-y-3"
|
|
7
13
|
>
|
|
8
14
|
<canvas
|
|
9
|
-
v-for="page in
|
|
15
|
+
v-for="page in (data as PDFDocumentProxy).numPages"
|
|
10
16
|
:key="page"
|
|
11
17
|
:ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
|
|
12
18
|
class="w-full"
|
|
13
19
|
/>
|
|
14
20
|
</div>
|
|
15
|
-
|
|
16
|
-
v-else-if="loading"
|
|
17
|
-
class="text-gray-medium"
|
|
18
|
-
>
|
|
19
|
-
{{ t("Chargement de l'aperçu PDF...") }}
|
|
20
|
-
</div>
|
|
21
|
-
<PreviewUnavailable v-else-if="fileTooLarge">
|
|
22
|
-
{{ fileSizeBytes
|
|
23
|
-
? t("Le fichier PDF est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.")
|
|
24
|
-
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
|
|
25
|
-
}}
|
|
26
|
-
</PreviewUnavailable>
|
|
27
|
-
<PreviewUnavailable v-else-if="error === 'network'">
|
|
28
|
-
{{ 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.") }}
|
|
29
|
-
</PreviewUnavailable>
|
|
30
|
-
<PreviewUnavailable v-else-if="error">
|
|
31
|
-
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
|
|
32
|
-
</PreviewUnavailable>
|
|
33
|
-
</div>
|
|
21
|
+
</PreviewWrapper>
|
|
34
22
|
</template>
|
|
35
23
|
|
|
36
24
|
<script setup lang="ts">
|
|
37
|
-
import {
|
|
25
|
+
import { onBeforeUnmount, ref } from 'vue'
|
|
38
26
|
import * as pdfjsLib from 'pdfjs-dist'
|
|
39
27
|
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
|
|
40
28
|
import type { PDFDocumentProxy } from 'pdfjs-dist'
|
|
41
|
-
import
|
|
29
|
+
import PreviewWrapper from './PreviewWrapper.vue'
|
|
42
30
|
import { useComponentsConfig } from '../../config'
|
|
43
31
|
import type { Resource } from '../../types/resources'
|
|
44
|
-
import { useTranslation } from '../../composables/useTranslation'
|
|
45
|
-
import { getResourceFilesize } from '../../functions/datasets'
|
|
46
32
|
|
|
47
33
|
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
|
|
48
34
|
|
|
@@ -51,15 +37,8 @@ const props = defineProps<{
|
|
|
51
37
|
}>()
|
|
52
38
|
|
|
53
39
|
const config = useComponentsConfig()
|
|
54
|
-
const { t } = useTranslation()
|
|
55
40
|
|
|
56
41
|
const containerRef = ref<HTMLElement | null>(null)
|
|
57
|
-
const pdfReady = ref(false)
|
|
58
|
-
const loading = ref(false)
|
|
59
|
-
const error = ref<string | null>(null)
|
|
60
|
-
const fileTooLarge = ref(false)
|
|
61
|
-
const totalPages = ref(0)
|
|
62
|
-
|
|
63
42
|
let pdfDoc: PDFDocumentProxy | null = null
|
|
64
43
|
const canvasRefs = new Map<number, HTMLCanvasElement>()
|
|
65
44
|
|
|
@@ -95,64 +74,22 @@ async function renderPage(pageNum: number) {
|
|
|
95
74
|
}).promise
|
|
96
75
|
}
|
|
97
76
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// Use maxPdfPreviewByteSize from config, fallback to 10 MB if not set
|
|
108
|
-
const maxByteSize = config.maxPdfPreviewByteSize ?? 10_000_000
|
|
109
|
-
return size <= maxByteSize
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
const loadPdf = async () => {
|
|
113
|
-
if (!shouldLoadPdf.value) {
|
|
114
|
-
fileTooLarge.value = true
|
|
115
|
-
return
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
loading.value = true
|
|
119
|
-
error.value = null
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const loadingTask = pdfjsLib.getDocument({
|
|
123
|
-
url: props.resource.url,
|
|
124
|
-
isEvalSupported: false,
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
pdfDoc = await loadingTask.promise
|
|
128
|
-
totalPages.value = pdfDoc.numPages
|
|
129
|
-
pdfReady.value = true
|
|
130
|
-
|
|
131
|
-
await nextTick()
|
|
77
|
+
const load = async () => {
|
|
78
|
+
const loadingTask = pdfjsLib.getDocument({
|
|
79
|
+
url: props.resource.url,
|
|
80
|
+
isEvalSupported: false,
|
|
81
|
+
})
|
|
82
|
+
pdfDoc = await loadingTask.promise
|
|
83
|
+
return pdfDoc
|
|
84
|
+
}
|
|
132
85
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
catch (err) {
|
|
138
|
-
console.error('Error loading PDF:', err)
|
|
139
|
-
|
|
140
|
-
if (err instanceof TypeError) {
|
|
141
|
-
error.value = 'network'
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
error.value = 'generic'
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
finally {
|
|
148
|
-
loading.value = false
|
|
86
|
+
const renderAllPages = async () => {
|
|
87
|
+
if (!pdfDoc) return
|
|
88
|
+
for (let i = 1; i <= pdfDoc.numPages; i++) {
|
|
89
|
+
await renderPage(i)
|
|
149
90
|
}
|
|
150
91
|
}
|
|
151
92
|
|
|
152
|
-
onMounted(() => {
|
|
153
|
-
loadPdf()
|
|
154
|
-
})
|
|
155
|
-
|
|
156
93
|
onBeforeUnmount(() => {
|
|
157
94
|
pdfDoc?.destroy()
|
|
158
95
|
})
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
>
|
|
49
49
|
<BrandedButton
|
|
50
50
|
color="tertiary"
|
|
51
|
-
:icon="isSortedBy(col) && sortConfig && sortConfig.
|
|
51
|
+
:icon="isSortedBy(col) && sortConfig && sortConfig.direction === 'asc' ? RiArrowUpLine : RiArrowDownLine"
|
|
52
52
|
icon-right
|
|
53
53
|
size="xs"
|
|
54
54
|
@click="sortByField(col)"
|
|
@@ -56,7 +56,7 @@
|
|
|
56
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… -->
|
|
57
57
|
<span class="relative">
|
|
58
58
|
{{ col }}
|
|
59
|
-
<span class="sr-only">{{ sortConfig && sortConfig.
|
|
59
|
+
<span class="sr-only">{{ sortConfig && sortConfig.direction === 'desc' ? t("Trier par ordre croissant") : t("Trier par ordre décroissant") }}</span>
|
|
60
60
|
</span>
|
|
61
61
|
</BrandedButton>
|
|
62
62
|
</th>
|
|
@@ -122,7 +122,7 @@ const rows = ref<Array<Record<string, unknown>>>([])
|
|
|
122
122
|
const columns = ref<Array<string>>([])
|
|
123
123
|
const loading = ref(true)
|
|
124
124
|
const hasError = ref(false)
|
|
125
|
-
const sortConfig = ref<SortConfig>(null)
|
|
125
|
+
const sortConfig = ref<SortConfig | null>(null)
|
|
126
126
|
const rowCount = ref(0)
|
|
127
127
|
const config = useComponentsConfig()
|
|
128
128
|
const pageSize = computed(() => config.tabularApiPageSize || 15)
|
|
@@ -138,11 +138,11 @@ function isSortedBy(col: string) {
|
|
|
138
138
|
/**
|
|
139
139
|
* Retrieve preview necessary infos
|
|
140
140
|
*/
|
|
141
|
-
async function getTableInfos(page: number, sortConfig?: SortConfig) {
|
|
141
|
+
async function getTableInfos(page: number, sortConfig?: SortConfig | null) {
|
|
142
142
|
try {
|
|
143
143
|
// Check that this function return wanted data
|
|
144
144
|
const response = await getData(config, props.resource.id, page, sortConfig)
|
|
145
|
-
if ('data' in response && response.data && response.data
|
|
145
|
+
if ('data' in response && response.data && 0 in response.data) {
|
|
146
146
|
// Update existing rows
|
|
147
147
|
rows.value = response.data
|
|
148
148
|
columns.value = Object.keys(response.data[0]).filter(item => item !== '__id')
|
|
@@ -172,24 +172,24 @@ function changePage(page: number) {
|
|
|
172
172
|
* Sort by a specific column
|
|
173
173
|
*/
|
|
174
174
|
function sortByField(col: string) {
|
|
175
|
-
if (sortConfig.value && sortConfig.value.column
|
|
176
|
-
if (sortConfig.value.
|
|
177
|
-
sortConfig.value.
|
|
175
|
+
if (sortConfig.value && sortConfig.value.column === col) {
|
|
176
|
+
if (sortConfig.value.direction === 'asc') {
|
|
177
|
+
sortConfig.value.direction = 'desc'
|
|
178
178
|
}
|
|
179
179
|
else {
|
|
180
|
-
sortConfig.value.
|
|
180
|
+
sortConfig.value.direction = 'asc'
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
else {
|
|
184
184
|
if (!sortConfig.value) {
|
|
185
185
|
sortConfig.value = {
|
|
186
186
|
column: col,
|
|
187
|
-
|
|
187
|
+
direction: 'asc',
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
else {
|
|
191
191
|
sortConfig.value.column = col
|
|
192
|
-
sortConfig.value.
|
|
192
|
+
sortConfig.value.direction = 'asc'
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
currentPage.value = 1
|