@datagouv/components-next 1.0.2-dev.2 → 1.0.2-dev.200
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-CFTmBFoY.js +30 -0
- 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-BvDM3qjE.js +36 -0
- package/dist/JsonPreview.client-BP6gPRxf.js +40 -0
- package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
- package/dist/MapContainer.client-D9N3664r.js +101 -0
- package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
- package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-C6A6ZiKl.js} +835 -871
- package/dist/Pmtiles.client-B_sDZoqu.js +26333 -0
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-DGLXBys_.js +60 -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-DFQ1T-LN.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 +196 -150
- package/dist/components.css +2 -2
- package/dist/{index-SrYZwgCT.js → index-B_tPYi5Z.js} +34094 -27864
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-PeylHk0o.js} +162 -164
- package/dist/main-CVOZp_Nx.js +75398 -0
- 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-BRxsqI9j.js → vue3-xml-viewer.common-CcF3Xd23.js} +1 -1
- package/package.json +50 -37
- package/src/chart.ts +5 -0
- package/src/components/ActivityList/ActivityList.vue +24 -14
- package/src/components/AppLink.vue +3 -0
- package/src/components/BannerAction.vue +6 -2
- package/src/components/Chart/ChartViewer.vue +320 -0
- package/src/components/Chart/ChartViewerWrapper.vue +182 -0
- package/src/components/CopyButton.vue +6 -2
- package/src/components/DataserviceCard.vue +24 -5
- 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 +24 -9
- package/src/components/DatasetInformation/DatasetTemporalitySection.vue +7 -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/DiscussionMessageCard.vue +14 -15
- package/src/components/Form/Listbox.vue +101 -0
- package/src/components/Form/SchemaSelect.vue +2 -0
- package/src/components/Form/SearchableSelect.vue +2 -1
- package/src/components/FormattedDate.vue +81 -0
- package/src/components/InfiniteLoader.vue +53 -0
- package/src/components/LoadingBlock.vue +4 -1
- 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 +25 -62
- package/src/components/PostCard.vue +13 -15
- 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/Datafair.client.vue +4 -10
- package/src/components/ResourceAccordion/Downloads.vue +172 -0
- package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
- package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
- package/src/components/ResourceAccordion/MapContainer.client.vue +6 -19
- package/src/components/ResourceAccordion/Metadata.vue +4 -6
- package/src/components/ResourceAccordion/PdfPreview.client.vue +58 -113
- package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
- package/src/components/ResourceAccordion/Pmtiles.client.vue +33 -23
- package/src/components/ResourceAccordion/Preview.vue +24 -25
- package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
- package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
- package/src/components/ResourceAccordion/PreviewWrapper.vue +89 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +36 -117
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
- package/src/components/ResourceExplorer/ResourceExplorer.vue +105 -199
- package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +79 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +104 -71
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +143 -280
- package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
- package/src/components/ResourceExplorer/ResourceSelector.vue +51 -0
- package/src/components/ResourceExplorer/ResourceViewerHeader.vue +122 -0
- package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +94 -0
- package/src/components/ResourceListItem.vue +168 -0
- package/src/components/ReuseCard.vue +28 -9
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/ReuseHorizontalCard.vue +2 -2
- package/src/components/SchemaCard.vue +10 -1
- package/src/components/Search/Filter/AccessTypeFilter.vue +2 -2
- package/src/components/Search/GlobalSearch.vue +229 -123
- package/src/components/Search/SearchInput.vue +5 -4
- package/src/components/Search/Sidemenu.vue +4 -3
- package/src/components/SmallChart.vue +11 -10
- package/src/components/StatBox.vue +13 -5
- package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
- package/src/components/TabularExplorer/TabularCell.vue +50 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +299 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +362 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
- package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
- package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
- package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
- package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
- package/src/components/TabularExplorer/TabularTable.vue +481 -0
- package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
- package/src/components/TabularExplorer/types.ts +84 -0
- package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
- package/src/components/TabularExplorer/useTabularContext.ts +82 -0
- package/src/components/TopicCard.vue +13 -14
- package/src/composables/useActiveDescendant.ts +5 -0
- package/src/composables/useDatasetResources.ts +164 -0
- package/src/composables/useHasTabularData.ts +15 -1
- package/src/composables/useMetrics.ts +1 -1
- package/src/composables/useResizable.ts +46 -0
- package/src/composables/useResourceCapabilities.ts +49 -41
- package/src/composables/useSearchFilter.ts +120 -0
- package/src/composables/useStableQueryParams.ts +38 -6
- package/src/composables/useTabularProfile.ts +70 -0
- package/src/composables/useTranslation.ts +24 -9
- 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 +6 -7
- package/src/functions/helpers.ts +15 -0
- package/src/functions/metrics.ts +63 -60
- package/src/functions/paginate.ts +46 -0
- package/src/functions/resourceCapabilities.ts +34 -0
- package/src/functions/resources.ts +136 -1
- package/src/functions/schemas.ts +27 -0
- package/src/functions/scroll.ts +20 -0
- package/src/functions/tabular.ts +214 -0
- package/src/functions/tabularApi.ts +96 -11
- package/src/main.ts +119 -14
- package/src/types/dataservices.ts +17 -0
- package/src/types/organizations.ts +11 -1
- 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/Datafair.client-E5D6ePRC.js +0 -35
- package/dist/JsonPreview.client-C-6eBbPw.js +0 -87
- package/dist/MapContainer.client-DRkAmdOc.js +0 -105
- package/dist/Pmtiles.client-BR7_ldHY.js +0 -25190
- package/dist/Swagger.client-D4-F6yEf.js +0 -4
- package/dist/XmlPreview.client-Dl2VCgXF.js +0 -79
- package/dist/main-B2kXxWRG.js +0 -105833
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
- /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
- /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
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
|
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.
|
|
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>
|
|
@@ -95,7 +90,11 @@
|
|
|
95
90
|
@change="changePage"
|
|
96
91
|
/>
|
|
97
92
|
<div class="fr-px-5v">
|
|
98
|
-
|
|
93
|
+
<TranslationT keypath="Dernière mise à jour de la prévisualisation : {date}">
|
|
94
|
+
<template #date>
|
|
95
|
+
<FormattedDate :date="parsingFinishedAt" />
|
|
96
|
+
</template>
|
|
97
|
+
</TranslationT> —
|
|
99
98
|
{{ t('{count} colonnes', columns.length) }} —
|
|
100
99
|
{{ t('Lignes {count}', rowCount) }}
|
|
101
100
|
</div>
|
|
@@ -105,15 +104,16 @@
|
|
|
105
104
|
|
|
106
105
|
<script setup lang="ts">
|
|
107
106
|
import { computed, onMounted, ref } from 'vue'
|
|
108
|
-
import { RiArrowDownLine, RiArrowUpLine,
|
|
107
|
+
import { RiArrowDownLine, RiArrowUpLine, RiExternalLinkFill } from '@remixicon/vue'
|
|
109
108
|
import Pagination from '../Pagination.vue'
|
|
110
109
|
import { getData, type SortConfig } from '../../functions/tabularApi'
|
|
111
|
-
import
|
|
110
|
+
import FormattedDate from '../FormattedDate.vue'
|
|
111
|
+
import TranslationT from '../TranslationT.vue'
|
|
112
112
|
import { trackEvent } from '../../functions/matomo'
|
|
113
113
|
import type { Resource } from '../../types/resources'
|
|
114
114
|
import { useComponentsConfig } from '../../config'
|
|
115
115
|
import BrandedButton from '../BrandedButton.vue'
|
|
116
|
-
import
|
|
116
|
+
import PreviewUnavailable from './PreviewUnavailable.vue'
|
|
117
117
|
import { useTranslation } from '../../composables/useTranslation'
|
|
118
118
|
import franceSvg from './france.svg?raw'
|
|
119
119
|
import PreviewLoader from './PreviewLoader.vue'
|
|
@@ -121,13 +121,12 @@ import PreviewLoader from './PreviewLoader.vue'
|
|
|
121
121
|
const props = defineProps<{ resource: Resource }>()
|
|
122
122
|
|
|
123
123
|
const { t } = useTranslation()
|
|
124
|
-
const { formatDate } = useFormatDate()
|
|
125
124
|
|
|
126
125
|
const rows = ref<Array<Record<string, unknown>>>([])
|
|
127
126
|
const columns = ref<Array<string>>([])
|
|
128
127
|
const loading = ref(true)
|
|
129
128
|
const hasError = ref(false)
|
|
130
|
-
const sortConfig = ref<SortConfig>(null)
|
|
129
|
+
const sortConfig = ref<SortConfig | null>(null)
|
|
131
130
|
const rowCount = ref(0)
|
|
132
131
|
const config = useComponentsConfig()
|
|
133
132
|
const pageSize = computed(() => config.tabularApiPageSize || 15)
|
|
@@ -143,11 +142,11 @@ function isSortedBy(col: string) {
|
|
|
143
142
|
/**
|
|
144
143
|
* Retrieve preview necessary infos
|
|
145
144
|
*/
|
|
146
|
-
async function getTableInfos(page: number, sortConfig?: SortConfig) {
|
|
145
|
+
async function getTableInfos(page: number, sortConfig?: SortConfig | null) {
|
|
147
146
|
try {
|
|
148
147
|
// Check that this function return wanted data
|
|
149
148
|
const response = await getData(config, props.resource.id, page, sortConfig)
|
|
150
|
-
if ('data' in response && response.data && response.data
|
|
149
|
+
if ('data' in response && response.data && 0 in response.data) {
|
|
151
150
|
// Update existing rows
|
|
152
151
|
rows.value = response.data
|
|
153
152
|
columns.value = Object.keys(response.data[0]).filter(item => item !== '__id')
|
|
@@ -177,31 +176,31 @@ function changePage(page: number) {
|
|
|
177
176
|
* Sort by a specific column
|
|
178
177
|
*/
|
|
179
178
|
function sortByField(col: string) {
|
|
180
|
-
if (sortConfig.value && sortConfig.value.column
|
|
181
|
-
if (sortConfig.value.
|
|
182
|
-
sortConfig.value.
|
|
179
|
+
if (sortConfig.value && sortConfig.value.column === col) {
|
|
180
|
+
if (sortConfig.value.direction === 'asc') {
|
|
181
|
+
sortConfig.value.direction = 'desc'
|
|
183
182
|
}
|
|
184
183
|
else {
|
|
185
|
-
sortConfig.value.
|
|
184
|
+
sortConfig.value.direction = 'asc'
|
|
186
185
|
}
|
|
187
186
|
}
|
|
188
187
|
else {
|
|
189
188
|
if (!sortConfig.value) {
|
|
190
189
|
sortConfig.value = {
|
|
191
190
|
column: col,
|
|
192
|
-
|
|
191
|
+
direction: 'asc',
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
else {
|
|
196
195
|
sortConfig.value.column = col
|
|
197
|
-
sortConfig.value.
|
|
196
|
+
sortConfig.value.direction = 'asc'
|
|
198
197
|
}
|
|
199
198
|
}
|
|
200
199
|
currentPage.value = 1
|
|
201
200
|
getTableInfos(currentPage.value, sortConfig.value)
|
|
202
201
|
};
|
|
203
202
|
|
|
204
|
-
const
|
|
203
|
+
const parsingFinishedAt = computed(() => props.resource.extras['analysis:parsing:finished_at'] as string | undefined)
|
|
205
204
|
|
|
206
205
|
onMounted(() => {
|
|
207
206
|
getTableInfos(currentPage.value)
|
|
@@ -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,89 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="text-xs"
|
|
4
|
+
:class="fill ? 'flex min-h-0 flex-1 flex-col' : ''"
|
|
5
|
+
>
|
|
6
|
+
<slot
|
|
7
|
+
v-if="data !== null"
|
|
8
|
+
:data="data"
|
|
9
|
+
/>
|
|
10
|
+
<slot
|
|
11
|
+
v-else-if="loading"
|
|
12
|
+
name="loading"
|
|
13
|
+
>
|
|
14
|
+
<div class="text-gray-medium">
|
|
15
|
+
{{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
|
|
16
|
+
</div>
|
|
17
|
+
</slot>
|
|
18
|
+
<PreviewUnavailable v-else-if="!isSizeAllowed">
|
|
19
|
+
{{ fileSizeBytes
|
|
20
|
+
? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le pour le consulter.", { fileType })
|
|
21
|
+
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le pour le consulter.")
|
|
22
|
+
}}
|
|
23
|
+
</PreviewUnavailable>
|
|
24
|
+
<PreviewUnavailable v-else-if="corsStatus === 'blocked'">
|
|
25
|
+
{{ 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 pour le consulter.", { fileType }) }}
|
|
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 pour le consulter.") }}
|
|
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 pour le consulter.") }}
|
|
32
|
+
</PreviewUnavailable>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import { computed, nextTick, onMounted, ref } from 'vue'
|
|
38
|
+
import PreviewUnavailable from './PreviewUnavailable.vue'
|
|
39
|
+
import type { Resource } from '../../types/resources'
|
|
40
|
+
import { getResourceFilesize, getResourceCorsStatus } from '../../functions/resources'
|
|
41
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
42
|
+
|
|
43
|
+
const props = defineProps<{
|
|
44
|
+
fileType: string
|
|
45
|
+
resource: Resource
|
|
46
|
+
maxSize: number | undefined
|
|
47
|
+
load: () => Promise<unknown>
|
|
48
|
+
// Fill the available height (fullscreen) instead of sizing to content.
|
|
49
|
+
fill?: boolean
|
|
50
|
+
}>()
|
|
51
|
+
|
|
52
|
+
const emit = defineEmits<{
|
|
53
|
+
loaded: []
|
|
54
|
+
}>()
|
|
55
|
+
|
|
56
|
+
const { t } = useTranslation()
|
|
57
|
+
|
|
58
|
+
const data = ref<unknown>(null)
|
|
59
|
+
const loading = ref(false)
|
|
60
|
+
const error = ref<'network' | 'generic' | null>(null)
|
|
61
|
+
|
|
62
|
+
const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
|
|
63
|
+
const corsStatus = computed(() => getResourceCorsStatus(props.resource))
|
|
64
|
+
|
|
65
|
+
const isSizeAllowed = computed(() => {
|
|
66
|
+
const size = fileSizeBytes.value
|
|
67
|
+
const max = props.maxSize
|
|
68
|
+
if (!size || !max) return false
|
|
69
|
+
return size <= max
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
onMounted(async () => {
|
|
73
|
+
if (!isSizeAllowed.value || corsStatus.value === 'blocked') return
|
|
74
|
+
|
|
75
|
+
loading.value = true
|
|
76
|
+
try {
|
|
77
|
+
data.value = await props.load()
|
|
78
|
+
await nextTick()
|
|
79
|
+
emit('loaded')
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error('Error loading preview:', err)
|
|
83
|
+
error.value = err instanceof TypeError ? 'network' : 'generic'
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
loading.value = false
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
</script>
|
|
@@ -56,11 +56,21 @@
|
|
|
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
|
/>
|
|
63
|
-
<
|
|
63
|
+
<TranslationT
|
|
64
|
+
class="text-xs mb-0"
|
|
65
|
+
keypath="Mis à jour {date}"
|
|
66
|
+
>
|
|
67
|
+
<template #date>
|
|
68
|
+
<FormattedDate
|
|
69
|
+
:date="lastUpdate"
|
|
70
|
+
format="relative"
|
|
71
|
+
/>
|
|
72
|
+
</template>
|
|
73
|
+
</TranslationT>
|
|
64
74
|
<RiSubtractLine
|
|
65
75
|
aria-hidden="true"
|
|
66
76
|
class="size-3 fill-gray-medium"
|
|
@@ -197,7 +207,10 @@
|
|
|
197
207
|
:key="tab.key"
|
|
198
208
|
class="px-4"
|
|
199
209
|
>
|
|
200
|
-
<div
|
|
210
|
+
<div
|
|
211
|
+
v-if="tab.key === 'map'"
|
|
212
|
+
class="-mx-4 h-[600px]"
|
|
213
|
+
>
|
|
201
214
|
<Pmtiles
|
|
202
215
|
v-if="hasPmtiles"
|
|
203
216
|
:resource="resource"
|
|
@@ -224,6 +237,10 @@
|
|
|
224
237
|
v-else-if="resource.format && resource.format.toLowerCase() === 'xml'"
|
|
225
238
|
:resource="resource"
|
|
226
239
|
/>
|
|
240
|
+
<ImagePreview
|
|
241
|
+
v-else-if="isImagePreviewFormat(resource.format)"
|
|
242
|
+
:resource="resource"
|
|
243
|
+
/>
|
|
227
244
|
<!-- Show Datafair embedded preview (koumoul) -->
|
|
228
245
|
<DatafairPreview
|
|
229
246
|
v-else-if="hasDatafairPreview"
|
|
@@ -231,7 +248,7 @@
|
|
|
231
248
|
:dataset="dataset"
|
|
232
249
|
/>
|
|
233
250
|
<!-- Show Datafair embedded preview (koumoul) -->
|
|
234
|
-
<
|
|
251
|
+
<OpenApiViewer
|
|
235
252
|
v-else-if="hasOpenAPIPreview"
|
|
236
253
|
:url="resource.extras['apidocUrl'] as string"
|
|
237
254
|
/>
|
|
@@ -265,91 +282,13 @@
|
|
|
265
282
|
<div
|
|
266
283
|
v-if="tab.key === 'downloads'"
|
|
267
284
|
>
|
|
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>
|
|
285
|
+
<Downloads
|
|
286
|
+
:resource="resource"
|
|
287
|
+
:dataset="dataset"
|
|
288
|
+
/>
|
|
350
289
|
</div>
|
|
351
290
|
<div
|
|
352
|
-
v-if="tab.key === '
|
|
291
|
+
v-if="tab.key === 'api'"
|
|
353
292
|
>
|
|
354
293
|
<div class="fr-mb-4w">
|
|
355
294
|
<p>{{ t("Cette API est générée automatiquement par {platform} à partir du fichier.", { platform: config.name }) }}</p>
|
|
@@ -357,7 +296,7 @@
|
|
|
357
296
|
<p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
|
|
358
297
|
<p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
|
|
359
298
|
</div>
|
|
360
|
-
<
|
|
299
|
+
<OpenApiViewer
|
|
361
300
|
v-if="hasTabularData"
|
|
362
301
|
:url="`${config.tabularApiUrl}/api/resources/${props.resource.id}/swagger/`"
|
|
363
302
|
/>
|
|
@@ -374,7 +313,8 @@ import { ref, computed, defineAsyncComponent } from 'vue'
|
|
|
374
313
|
import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiSubtractLine } from '@remixicon/vue'
|
|
375
314
|
import OrganizationNameWithCertificate from '../OrganizationNameWithCertificate.vue'
|
|
376
315
|
import { filesize, summarize } from '../../functions/helpers'
|
|
377
|
-
import
|
|
316
|
+
import FormattedDate from '../FormattedDate.vue'
|
|
317
|
+
import TranslationT from '../TranslationT.vue'
|
|
378
318
|
import MarkdownViewer from '../MarkdownViewer.vue'
|
|
379
319
|
import type { CommunityResource, Resource } from '../../types/resources'
|
|
380
320
|
import type { Dataset, DatasetV2 } from '../../types/datasets'
|
|
@@ -387,9 +327,8 @@ import { trackEvent } from '../../functions/matomo'
|
|
|
387
327
|
import CopyButton from '../CopyButton.vue'
|
|
388
328
|
import { useComponentsConfig } from '../../config'
|
|
389
329
|
import { getOwnerName } from '../../functions/owned'
|
|
390
|
-
import { getResourceFormatIcon, getResourceTitleId, detectOgcService } from '../../functions/resources'
|
|
330
|
+
import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize, isImagePreviewFormat } from '../../functions/resources'
|
|
391
331
|
import BrandedButton from '../BrandedButton.vue'
|
|
392
|
-
import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
|
|
393
332
|
import { useTranslation } from '../../composables/useTranslation'
|
|
394
333
|
import { useHasTabularData } from '../../composables/useHasTabularData'
|
|
395
334
|
import Metadata from './Metadata.vue'
|
|
@@ -397,11 +336,11 @@ import SchemaBadge from './SchemaBadge.vue'
|
|
|
397
336
|
import ResourceIcon from './ResourceIcon.vue'
|
|
398
337
|
import EditButton from './EditButton.vue'
|
|
399
338
|
import DataStructure from './DataStructure.vue'
|
|
339
|
+
import Downloads from './Downloads.vue'
|
|
400
340
|
import Preview from './Preview.vue'
|
|
401
341
|
import { isOrganizationCertified } from '../../functions/organizations'
|
|
402
|
-
import
|
|
342
|
+
import OpenApiViewer from '../OpenApiViewer/OpenApiViewer.vue'
|
|
403
343
|
|
|
404
|
-
const GENERATED_FORMATS = ['parquet', 'pmtiles', 'geojson']
|
|
405
344
|
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
345
|
|
|
407
346
|
const props = withDefaults(defineProps<{
|
|
@@ -418,24 +357,23 @@ const props = withDefaults(defineProps<{
|
|
|
418
357
|
|
|
419
358
|
const config = useComponentsConfig()
|
|
420
359
|
|
|
421
|
-
const Swagger = defineAsyncComponent(() => import('./Swagger.client.vue'))
|
|
422
360
|
const MapContainer = defineAsyncComponent(() => import('./MapContainer.client.vue'))
|
|
423
361
|
const Pmtiles = defineAsyncComponent(() => import('./Pmtiles.client.vue'))
|
|
424
362
|
const JsonPreview = defineAsyncComponent(() => import('./JsonPreview.client.vue'))
|
|
425
363
|
const PdfPreview = defineAsyncComponent(() => import('./PdfPreview.client.vue'))
|
|
426
364
|
const XmlPreview = defineAsyncComponent(() => import('./XmlPreview.client.vue'))
|
|
365
|
+
const ImagePreview = defineAsyncComponent(() => import('./ImagePreview.client.vue'))
|
|
427
366
|
const DatafairPreview = defineAsyncComponent(() => import('./Datafair.client.vue'))
|
|
428
367
|
|
|
429
368
|
const { t } = useTranslation()
|
|
430
|
-
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
431
369
|
const checkTabularData = useHasTabularData()
|
|
432
370
|
|
|
433
371
|
const hasPreview = computed(() => {
|
|
434
|
-
// For JSON, PDF, and
|
|
372
|
+
// For JSON, PDF, XML, and image files, show preview.
|
|
435
373
|
// We cannot check for CORS issues here because we cannot use an async component here.
|
|
436
374
|
// 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
375
|
const format = props.resource.format?.toLowerCase()
|
|
438
|
-
return format === 'json' || format === 'pdf' || format === 'xml'
|
|
376
|
+
return format === 'json' || format === 'pdf' || format === 'xml' || isImagePreviewFormat(format)
|
|
439
377
|
})
|
|
440
378
|
|
|
441
379
|
const hasTabularData = computed(() => checkTabularData(props.resource))
|
|
@@ -462,24 +400,6 @@ const ogcService = computed(() => detectOgcService(props.resource))
|
|
|
462
400
|
|
|
463
401
|
const ogcWms = computed(() => ogcService.value === 'wms')
|
|
464
402
|
|
|
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
403
|
const open = ref(props.expandedOnMount)
|
|
484
404
|
const toggle = () => {
|
|
485
405
|
open.value = !open.value
|
|
@@ -515,7 +435,7 @@ const tabsOptions = computed(() => {
|
|
|
515
435
|
options.push({ key: 'downloads', label: t('Téléchargements') })
|
|
516
436
|
|
|
517
437
|
if (hasTabularData.value) {
|
|
518
|
-
options.push({ key: '
|
|
438
|
+
options.push({ key: 'api', label: t('API') })
|
|
519
439
|
}
|
|
520
440
|
|
|
521
441
|
return options
|
|
@@ -542,7 +462,6 @@ const communityResource = computed<CommunityResource | null>(() => {
|
|
|
542
462
|
const owner = computed(() => communityResource.value ? getOwnerName(communityResource.value) : null)
|
|
543
463
|
|
|
544
464
|
const lastUpdate = props.resource.last_modified
|
|
545
|
-
const conversionsLastUpdate = computed(() => formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined))
|
|
546
465
|
const availabilityChecked = props.resource.extras && 'check:available' in props.resource.extras
|
|
547
466
|
const resourceFilesize = computed(() => getResourceFilesize(props.resource))
|
|
548
467
|
|
|
@@ -1,59 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 {
|
|
44
|
-
import { RiErrorWarningLine } from '@remixicon/vue'
|
|
45
|
-
|
|
14
|
+
import { defineAsyncComponent } from 'vue'
|
|
46
15
|
import { useComponentsConfig } from '../../config'
|
|
47
|
-
import
|
|
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(
|
|
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
|
|
94
|
-
|
|
95
|
-
if (!
|
|
96
|
-
|
|
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>
|