@datagouv/components-next 0.2.0 → 1.0.0
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 +1 -1
- package/assets/main.css +56 -1
- package/dist/Control-BNCDn-8E.js +148 -0
- package/dist/{Datafair.client-x39O4yfF.js → Datafair.client-Dls5AHTE.js} +1 -1
- package/dist/Event-BOgJUhNR.js +738 -0
- package/dist/Image-BN-4XkIn.js +247 -0
- package/dist/{JsonPreview.client-BMsC5JcY.js → JsonPreview.client-DPDTs433.js} +14 -14
- package/dist/Map-BdT3i2C4.js +7609 -0
- package/dist/MapContainer.client-BdAzd7bj.js +105 -0
- package/dist/OSM-CamriM9b.js +71 -0
- package/dist/{PdfPreview.client-COOkEkRA.js → PdfPreview.client-CopqSDyt.js} +3 -3
- package/dist/{Pmtiles.client-BaiIo4VZ.js → Pmtiles.client-mF6xaOO_.js} +2 -2
- package/dist/ScaleLine-BiesrgOv.js +165 -0
- package/dist/Swagger.client-eJ7gpfZA.js +4 -0
- package/dist/Tile-DCuqwNOI.js +1206 -0
- package/dist/TileImage-CmZf8EdU.js +1067 -0
- package/dist/View-DcDc7N2K.js +2858 -0
- package/dist/{XmlPreview.client-CAdN0w_Y.js → XmlPreview.client-C0OgBkSq.js} +7 -7
- package/dist/common-C4rDcQpp.js +243 -0
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +153 -117
- package/dist/components.css +1 -1
- package/dist/{MapContainer.client-DeSo8EvG.js → index-BRGqW8aQ.js} +4975 -21416
- package/dist/leaflet-src-7m1mB8LI.js +6338 -0
- package/dist/{main-Dgri3TQL.js → main-CNHxAJ8J.js} +56758 -51450
- package/dist/proj-CKwYjU38.js +1569 -0
- package/dist/tilecoord-YW3qEH_j.js +884 -0
- package/dist/{vue3-xml-viewer.common-D6skc_Ai.js → vue3-xml-viewer.common-CmAdQfIy.js} +1 -1
- package/package.json +5 -1
- package/src/components/ActivityList/ActivityList.vue +6 -2
- package/src/components/AppLink.vue +4 -1
- package/src/components/Avatar.vue +2 -2
- package/src/components/AvatarWithName.vue +8 -4
- package/src/components/BouncingDots.vue +21 -0
- package/src/components/BrandedButton.vue +2 -0
- package/src/components/CopyButton.vue +19 -7
- package/src/components/DataserviceCard.vue +83 -118
- package/src/components/DatasetCard.vue +110 -171
- package/src/components/DatasetInformation/DatasetEmbedSection.vue +43 -0
- package/src/components/DatasetInformation/DatasetInformationSection.vue +73 -0
- package/src/components/DatasetInformation/DatasetSchemaSection.vue +74 -0
- package/src/components/DatasetInformation/DatasetSpatialSection.vue +59 -0
- package/src/components/DatasetInformation/DatasetTemporalitySection.vue +45 -0
- package/src/components/DatasetInformation/index.ts +5 -0
- package/src/components/DatasetQualityTooltipContent.vue +3 -3
- package/src/components/DescriptionList.vue +1 -4
- package/src/components/DescriptionListDetails.vue +5 -0
- package/src/components/DescriptionListTerm.vue +5 -0
- package/src/components/DiscussionMessageCard.vue +63 -0
- package/src/components/ExtraAccordion.vue +4 -4
- package/src/components/Form/BadgeSelect.vue +35 -0
- package/src/components/Form/FormatSelect.vue +28 -0
- package/src/components/Form/GeozoneSelect.vue +52 -0
- package/src/components/Form/GranularitySelect.vue +29 -0
- package/src/components/Form/LicenseSelect.vue +30 -0
- package/src/components/Form/OrganizationSelect.vue +62 -0
- package/src/components/Form/OrganizationTypeSelect.vue +34 -0
- package/src/components/Form/ReuseTopicSelect.vue +29 -0
- package/src/components/Form/SchemaSelect.vue +30 -0
- package/src/components/Form/SearchableSelect.vue +334 -0
- package/src/components/Form/SelectGroup.vue +132 -0
- package/src/components/Form/TagSelect.vue +38 -0
- package/src/components/LeafletMap.vue +31 -0
- package/src/components/LicenseBadge.vue +24 -0
- package/src/components/LoadingBlock.vue +23 -2
- package/src/components/MarkdownViewer.vue +3 -1
- package/src/components/ObjectCard.vue +42 -0
- package/src/components/ObjectCardBadge.vue +22 -0
- package/src/components/ObjectCardHeader.vue +35 -0
- package/src/components/ObjectCardOwner.vue +43 -0
- package/src/components/ObjectCardShortDescription.vue +28 -0
- package/src/components/OrganizationCard.vue +35 -20
- package/src/components/OrganizationLogo.vue +1 -1
- package/src/components/OrganizationNameWithCertificate.vue +13 -7
- package/src/components/OwnerTypeIcon.vue +1 -0
- package/src/components/Pagination.vue +1 -1
- package/src/components/Placeholder.vue +5 -2
- package/src/components/PostCard.vue +62 -0
- package/src/components/RadioGroup.vue +32 -0
- package/src/components/RadioInput.vue +64 -0
- package/src/components/ResourceAccordion/EditButton.vue +2 -3
- package/src/components/ResourceAccordion/MapContainer.client.vue +20 -16
- package/src/components/ResourceAccordion/Metadata.vue +11 -24
- package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
- package/src/components/ResourceAccordion/Preview.vue +1 -1
- package/src/components/ResourceAccordion/ResourceAccordion.vue +30 -20
- package/src/components/ResourceAccordion/ResourceIcon.vue +1 -0
- package/src/components/ResourceAccordion/SchemaBadge.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorer.vue +243 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +116 -0
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +361 -0
- package/src/components/ReuseCard.vue +8 -28
- package/src/components/ReuseHorizontalCard.vue +80 -0
- package/src/components/Search/BasicAndAdvancedFilters.vue +49 -0
- package/src/components/Search/Filter/AccessTypeFilter.vue +37 -0
- package/src/components/Search/Filter/DatasetBadgeFilter.vue +40 -0
- package/src/components/Search/Filter/FilterButtonGroup.vue +78 -0
- package/src/components/Search/Filter/FormatFamilyFilter.vue +39 -0
- package/src/components/Search/Filter/LastUpdateRangeFilter.vue +37 -0
- package/src/components/Search/Filter/ProducerTypeFilter.vue +39 -0
- package/src/components/Search/Filter/ReuseTypeFilter.vue +42 -0
- package/src/components/Search/GlobalSearch.vue +611 -0
- package/src/components/Search/SearchInput.vue +63 -0
- package/src/components/Search/Sidemenu.vue +38 -0
- package/src/components/StatBox.vue +5 -5
- package/src/components/Tag.vue +30 -0
- package/src/components/Toggletip.vue +6 -2
- package/src/components/Tooltip.vue +2 -3
- package/src/components/TopicCard.vue +134 -0
- package/src/components/radioGroupContext.ts +9 -0
- package/src/composables/useDebouncedRef.ts +31 -0
- package/src/composables/useMetrics.ts +4 -3
- package/src/composables/useResourceCapabilities.ts +118 -0
- package/src/composables/useRouteQueryBoolean.ts +10 -0
- package/src/composables/useSelectModelSync.ts +89 -0
- package/src/composables/useStableQueryParams.ts +84 -0
- package/src/config.ts +4 -0
- package/src/functions/api.ts +17 -6
- package/src/functions/api.types.ts +4 -2
- package/src/functions/datasets.ts +1 -29
- package/src/functions/description.ts +33 -0
- package/src/functions/helpers.ts +11 -0
- package/src/functions/markdown.ts +60 -16
- package/src/functions/metrics.ts +33 -0
- package/src/functions/organizations.ts +5 -5
- package/src/main.ts +89 -7
- package/src/types/dataservices.ts +14 -12
- package/src/types/datasets.ts +20 -7
- package/src/types/discussions.ts +20 -0
- package/src/types/licenses.ts +3 -3
- package/src/types/organizations.ts +13 -1
- package/src/types/owned.ts +4 -2
- package/src/types/pages.ts +70 -0
- package/src/types/posts.ts +27 -0
- package/src/types/resources.ts +6 -0
- package/src/types/reuses.ts +14 -5
- package/src/types/search.ts +379 -0
- package/src/types/users.ts +12 -3
- package/dist/Swagger.client-CpLgaLg6.js +0 -4
- package/src/components/DatasetInformationPanel.vue +0 -211
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="border border-gray-default">
|
|
3
|
+
<header class="p-4 flex flex-wrap md:flex-nowrap gap-4 items-center justify-between">
|
|
4
|
+
<div>
|
|
5
|
+
<div class="flex items-center mb-1">
|
|
6
|
+
<h3 class="m-0 flex items-baseline text-base font-bold leading-tight">
|
|
7
|
+
<ResourceIcon
|
|
8
|
+
:resource
|
|
9
|
+
class="size-3.5 mr-1"
|
|
10
|
+
/>
|
|
11
|
+
<span class="line-clamp-2">{{ resource.title || t('Fichier sans nom') }}</span>
|
|
12
|
+
</h3>
|
|
13
|
+
<CopyButton
|
|
14
|
+
:label="t('Copier le lien')"
|
|
15
|
+
:copied-label="t('Lien copié !')"
|
|
16
|
+
:text="resourceExternalUrl"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="text-gray-medium text-xs flex items-center gap-1">
|
|
20
|
+
<span>{{ t('mis à jour {date}', { date: formatRelativeIfRecentDate(resource.last_modified) }) }}</span>
|
|
21
|
+
<RiSubtractLine
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
class="size-3 fill-gray-medium"
|
|
24
|
+
/>
|
|
25
|
+
<template v-if="resource.format">
|
|
26
|
+
<span>
|
|
27
|
+
{{ resource.format.trim().toLowerCase() }}
|
|
28
|
+
<span v-if="resourceFilesize">({{ filesize(resourceFilesize) }})</span>
|
|
29
|
+
</span>
|
|
30
|
+
<RiSubtractLine
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
class="size-3 fill-gray-medium"
|
|
33
|
+
/>
|
|
34
|
+
</template>
|
|
35
|
+
<span class="inline-flex items-center">
|
|
36
|
+
<RiDownloadLine class="size-3 mr-0.5" />
|
|
37
|
+
{{ summarize(resource.metrics.views) }}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="flex items-center gap-2">
|
|
42
|
+
<BrandedButton
|
|
43
|
+
v-if="isResourceUrl"
|
|
44
|
+
:href="resource.latest"
|
|
45
|
+
:title="t('Lien du fichier - ouvre une nouvelle fenêtre')"
|
|
46
|
+
rel="ugc nofollow noopener"
|
|
47
|
+
new-tab
|
|
48
|
+
size="xs"
|
|
49
|
+
external
|
|
50
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
|
|
51
|
+
>
|
|
52
|
+
{{ t('Visiter') }}
|
|
53
|
+
</BrandedButton>
|
|
54
|
+
<BrandedButton
|
|
55
|
+
v-else-if="ogcService"
|
|
56
|
+
:icon="RiFileCopyLine"
|
|
57
|
+
color="primary"
|
|
58
|
+
size="xs"
|
|
59
|
+
@click="copyResourceUrl"
|
|
60
|
+
>
|
|
61
|
+
{{ t('Copier le lien') }}
|
|
62
|
+
</BrandedButton>
|
|
63
|
+
<BrandedButton
|
|
64
|
+
v-else
|
|
65
|
+
:href="resource.latest"
|
|
66
|
+
rel="ugc nofollow noopener"
|
|
67
|
+
:title="downloadButtonTitle"
|
|
68
|
+
download
|
|
69
|
+
class="matomo_download"
|
|
70
|
+
:icon="unavailable ? RiFileWarningLine : RiDownloadLine"
|
|
71
|
+
size="xs"
|
|
72
|
+
color="primary"
|
|
73
|
+
external
|
|
74
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
|
|
75
|
+
>
|
|
76
|
+
{{ t('Télécharger') }}
|
|
77
|
+
</BrandedButton>
|
|
78
|
+
</div>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<section class="pb-4">
|
|
82
|
+
<TabGroup
|
|
83
|
+
size="sm"
|
|
84
|
+
@change="switchTab"
|
|
85
|
+
>
|
|
86
|
+
<div class="pl-4 pr-4 pb-4">
|
|
87
|
+
<TabList class="max-w-full overflow-x-auto">
|
|
88
|
+
<Tab
|
|
89
|
+
v-for="tab in tabsOptions"
|
|
90
|
+
:key="tab.key"
|
|
91
|
+
>
|
|
92
|
+
{{ tab.label }}
|
|
93
|
+
</Tab>
|
|
94
|
+
</TabList>
|
|
95
|
+
</div>
|
|
96
|
+
<TabPanels>
|
|
97
|
+
<TabPanel
|
|
98
|
+
v-for="tab in tabsOptions"
|
|
99
|
+
:key="tab.key"
|
|
100
|
+
class="px-4"
|
|
101
|
+
>
|
|
102
|
+
<div v-if="tab.key === 'map'">
|
|
103
|
+
<Pmtiles
|
|
104
|
+
v-if="hasPmtiles"
|
|
105
|
+
:resource="resource"
|
|
106
|
+
:dataset="dataset"
|
|
107
|
+
/>
|
|
108
|
+
<MapContainer
|
|
109
|
+
v-if="ogcWms"
|
|
110
|
+
:resource="resource"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
<div v-if="tab.key === 'data'">
|
|
114
|
+
<JsonPreview
|
|
115
|
+
v-if="resource.format && resource.format.toLowerCase() === 'json'"
|
|
116
|
+
:resource="resource"
|
|
117
|
+
/>
|
|
118
|
+
<PdfPreview
|
|
119
|
+
v-else-if="resource.format && resource.format.toLowerCase() === 'pdf'"
|
|
120
|
+
:resource="resource"
|
|
121
|
+
/>
|
|
122
|
+
<XmlPreview
|
|
123
|
+
v-else-if="resource.format && resource.format.toLowerCase() === 'xml'"
|
|
124
|
+
:resource="resource"
|
|
125
|
+
/>
|
|
126
|
+
<DatafairPreview
|
|
127
|
+
v-else-if="hasDatafairPreview"
|
|
128
|
+
:resource="resource"
|
|
129
|
+
:dataset="dataset"
|
|
130
|
+
/>
|
|
131
|
+
<SwaggerClient
|
|
132
|
+
v-else-if="hasOpenAPIPreview"
|
|
133
|
+
:url="resource.extras['apidocUrl'] as string"
|
|
134
|
+
/>
|
|
135
|
+
<Preview
|
|
136
|
+
v-else
|
|
137
|
+
:resource="resource"
|
|
138
|
+
/>
|
|
139
|
+
</div>
|
|
140
|
+
<div v-if="tab.key === 'description'">
|
|
141
|
+
<MarkdownViewer
|
|
142
|
+
:content="resource.description || ''"
|
|
143
|
+
size="sm"
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
<div v-if="tab.key === 'data-structure'">
|
|
147
|
+
<DataStructure
|
|
148
|
+
v-if="hasTabularData"
|
|
149
|
+
:resource="resource"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
<div v-if="tab.key === 'metadata'">
|
|
153
|
+
<Metadata :resource />
|
|
154
|
+
</div>
|
|
155
|
+
<div v-if="tab.key === 'downloads'">
|
|
156
|
+
<dl class="fr-pl-0">
|
|
157
|
+
<dt
|
|
158
|
+
v-if="resource.format === 'url'"
|
|
159
|
+
class="font-bold fr-text--sm fr-mb-0"
|
|
160
|
+
>
|
|
161
|
+
{{ t("URL d'origine") }}
|
|
162
|
+
</dt>
|
|
163
|
+
<dt
|
|
164
|
+
v-else
|
|
165
|
+
class="font-bold fr-text--sm fr-mb-0"
|
|
166
|
+
>
|
|
167
|
+
{{ t('Format original') }}
|
|
168
|
+
</dt>
|
|
169
|
+
<dd class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center">
|
|
170
|
+
<span
|
|
171
|
+
v-if="resource.format === 'url'"
|
|
172
|
+
class="inline-flex items-center max-w-full"
|
|
173
|
+
>
|
|
174
|
+
<a
|
|
175
|
+
:href="resource.latest"
|
|
176
|
+
class="fr-link no-icon-after truncate"
|
|
177
|
+
rel="ugc nofollow noopener"
|
|
178
|
+
target="_blank"
|
|
179
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
|
|
180
|
+
>
|
|
181
|
+
{{ resource.url }}
|
|
182
|
+
</a>
|
|
183
|
+
<span class="fr-ml-1v fr-icon-external-link-line fr-icon--sm shrink-0" />
|
|
184
|
+
</span>
|
|
185
|
+
<span v-else>
|
|
186
|
+
<span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
|
|
187
|
+
<a
|
|
188
|
+
:href="resource.latest"
|
|
189
|
+
class="fr-link"
|
|
190
|
+
rel="ugc nofollow noopener"
|
|
191
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${resource.format}`)"
|
|
192
|
+
>
|
|
193
|
+
<span>{{ t('Format {format}', { format: resource.format }) }}<span v-if="resourceFilesize"> - {{ filesize(resourceFilesize) }}</span></span>
|
|
194
|
+
</a>
|
|
195
|
+
</span>
|
|
196
|
+
<CopyButton
|
|
197
|
+
:label="t('Copier le lien')"
|
|
198
|
+
:copied-label="t('Lien copié !')"
|
|
199
|
+
:text="resource.latest"
|
|
200
|
+
class="relative"
|
|
201
|
+
/>
|
|
202
|
+
</dd>
|
|
203
|
+
<template v-if="generatedFormats.length">
|
|
204
|
+
<dt class="font-bold fr-text--sm fr-mb-0">
|
|
205
|
+
{{ t('Formats générés automatiquement par {platform} (dernière mise à jour {date})', { platform: config.name, date: conversionsLastUpdate }) }}
|
|
206
|
+
</dt>
|
|
207
|
+
<dd
|
|
208
|
+
v-for="generatedFormat in generatedFormats"
|
|
209
|
+
:key="generatedFormat.format"
|
|
210
|
+
class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
|
|
211
|
+
>
|
|
212
|
+
<span>
|
|
213
|
+
<span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
|
|
214
|
+
<a
|
|
215
|
+
:href="generatedFormat.url"
|
|
216
|
+
class="fr-link"
|
|
217
|
+
rel="ugc nofollow noopener"
|
|
218
|
+
@click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${generatedFormat.format}`)"
|
|
219
|
+
>
|
|
220
|
+
<span>{{ t('Format {format}', { format: generatedFormat.format }) }}<span v-if="generatedFormat.size"> - {{ filesize(generatedFormat.size) }}</span></span>
|
|
221
|
+
</a>
|
|
222
|
+
</span>
|
|
223
|
+
<CopyButton
|
|
224
|
+
:label="t('Copier le lien')"
|
|
225
|
+
:copied-label="t('Lien copié !')"
|
|
226
|
+
:text="generatedFormat.url"
|
|
227
|
+
class="relative"
|
|
228
|
+
/>
|
|
229
|
+
</dd>
|
|
230
|
+
</template>
|
|
231
|
+
</dl>
|
|
232
|
+
</div>
|
|
233
|
+
<div v-if="tab.key === 'swagger'">
|
|
234
|
+
<div class="fr-mb-4w">
|
|
235
|
+
<p>{{ t("Cette API est générée automatiquement par {platform} à partir du fichier.", { platform: config.name }) }}</p>
|
|
236
|
+
<p>{{ t("- Si le fichier est modifié, l'API sera mise à jour et sa structure pourra changer.") }}</p>
|
|
237
|
+
<p>{{ t("- Si le fichier est supprimé, l'API sera également supprimée.") }}</p>
|
|
238
|
+
<p>{{ t("Pour des usages pérennes, prévoyez que cette API dépend directement du fichier source.") }}</p>
|
|
239
|
+
</div>
|
|
240
|
+
<Swagger
|
|
241
|
+
v-if="hasTabularData"
|
|
242
|
+
:url="`${config.tabularApiUrl}/api/resources/${resource.id}/swagger/`"
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
</TabPanel>
|
|
246
|
+
</TabPanels>
|
|
247
|
+
</TabGroup>
|
|
248
|
+
</section>
|
|
249
|
+
</div>
|
|
250
|
+
</template>
|
|
251
|
+
|
|
252
|
+
<script setup lang="ts">
|
|
253
|
+
import { computed, defineAsyncComponent } from 'vue'
|
|
254
|
+
import { RiDownloadLine, RiFileCopyLine, RiFileWarningLine, RiSubtractLine } from '@remixicon/vue'
|
|
255
|
+
import { toast } from 'vue-sonner'
|
|
256
|
+
import BrandedButton from '../BrandedButton.vue'
|
|
257
|
+
import CopyButton from '../CopyButton.vue'
|
|
258
|
+
import MarkdownViewer from '../MarkdownViewer.vue'
|
|
259
|
+
import ResourceIcon from '../ResourceAccordion/ResourceIcon.vue'
|
|
260
|
+
import Swagger from '../ResourceAccordion/Swagger.client.vue'
|
|
261
|
+
import TabGroup from '../Tabs/TabGroup.vue'
|
|
262
|
+
import TabList from '../Tabs/TabList.vue'
|
|
263
|
+
import Tab from '../Tabs/Tab.vue'
|
|
264
|
+
import TabPanels from '../Tabs/TabPanels.vue'
|
|
265
|
+
import TabPanel from '../Tabs/TabPanel.vue'
|
|
266
|
+
import Preview from '../ResourceAccordion/Preview.vue'
|
|
267
|
+
import DataStructure from '../ResourceAccordion/DataStructure.vue'
|
|
268
|
+
import Metadata from '../ResourceAccordion/Metadata.vue'
|
|
269
|
+
import { filesize, summarize } from '../../functions/helpers'
|
|
270
|
+
import { getResourceFormatIcon } from '../../functions/resources'
|
|
271
|
+
import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
|
|
272
|
+
import { trackEvent } from '../../functions/matomo'
|
|
273
|
+
import { useComponentsConfig } from '../../config'
|
|
274
|
+
import { useFormatDate } from '../../functions/dates'
|
|
275
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
276
|
+
import { useResourceCapabilities } from '../../composables/useResourceCapabilities'
|
|
277
|
+
import type { Resource } from '../../types/resources'
|
|
278
|
+
import type { Dataset, DatasetV2 } from '../../types/datasets'
|
|
279
|
+
|
|
280
|
+
const JsonPreview = defineAsyncComponent(() =>
|
|
281
|
+
import('../ResourceAccordion/JsonPreview.client.vue'),
|
|
282
|
+
)
|
|
283
|
+
const PdfPreview = defineAsyncComponent(() =>
|
|
284
|
+
import('../ResourceAccordion/PdfPreview.client.vue'),
|
|
285
|
+
)
|
|
286
|
+
const XmlPreview = defineAsyncComponent(() =>
|
|
287
|
+
import('../ResourceAccordion/XmlPreview.client.vue'),
|
|
288
|
+
)
|
|
289
|
+
const DatafairPreview = defineAsyncComponent(() =>
|
|
290
|
+
import('../ResourceAccordion/Datafair.client.vue'),
|
|
291
|
+
)
|
|
292
|
+
const MapContainer = defineAsyncComponent(() =>
|
|
293
|
+
import('../ResourceAccordion/MapContainer.client.vue'),
|
|
294
|
+
)
|
|
295
|
+
const Pmtiles = defineAsyncComponent(() =>
|
|
296
|
+
import('../ResourceAccordion/Pmtiles.client.vue'),
|
|
297
|
+
)
|
|
298
|
+
const SwaggerClient = defineAsyncComponent(() =>
|
|
299
|
+
import('../ResourceAccordion/Swagger.client.vue'),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
const props = defineProps<{
|
|
303
|
+
dataset: Dataset | DatasetV2
|
|
304
|
+
resource: Resource
|
|
305
|
+
}>()
|
|
306
|
+
|
|
307
|
+
const { t } = useTranslation()
|
|
308
|
+
const config = useComponentsConfig()
|
|
309
|
+
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
310
|
+
|
|
311
|
+
const {
|
|
312
|
+
hasTabularData,
|
|
313
|
+
hasPmtiles,
|
|
314
|
+
hasDatafairPreview,
|
|
315
|
+
hasOpenAPIPreview,
|
|
316
|
+
ogcService,
|
|
317
|
+
ogcWms,
|
|
318
|
+
generatedFormats,
|
|
319
|
+
isResourceUrl,
|
|
320
|
+
tabsOptions,
|
|
321
|
+
} = useResourceCapabilities(() => props.resource, () => props.dataset)
|
|
322
|
+
|
|
323
|
+
const resourceFilesize = computed(() => getResourceFilesize(props.resource))
|
|
324
|
+
const resourceExternalUrl = computed(() => getResourceExternalUrl(props.dataset, props.resource))
|
|
325
|
+
|
|
326
|
+
const format = computed(() => getResourceFormatIcon(props.resource.format) ? props.resource.format : 'Fichier')
|
|
327
|
+
const availabilityChecked = computed(() => props.resource.extras && 'check:available' in props.resource.extras)
|
|
328
|
+
const unavailable = computed(() => availabilityChecked.value && props.resource.extras['check:available'] === false)
|
|
329
|
+
const downloadButtonTitle = computed(() => {
|
|
330
|
+
if (unavailable.value) {
|
|
331
|
+
return t('Le robot de {platform} n\'a pas pu accéder à ce fichier - Télécharger le fichier en {format}', { platform: config.name, format: format.value })
|
|
332
|
+
}
|
|
333
|
+
return t('Télécharger le fichier en {format}', { format: format.value })
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
const conversionsLastUpdate = computed(() =>
|
|
337
|
+
formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined),
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
const copyResourceUrl = async () => {
|
|
341
|
+
try {
|
|
342
|
+
await navigator.clipboard.writeText(props.resource.url)
|
|
343
|
+
toast.success(t('Lien copié !'))
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
toast.error(t('Impossible de copier dans le presse-papier'))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const switchTab = (index: number) => {
|
|
351
|
+
const option = tabsOptions.value[index]
|
|
352
|
+
if (!option) return
|
|
353
|
+
trackEvent('View resource tab', props.resource.id, option.label)
|
|
354
|
+
if (option.key === 'data') {
|
|
355
|
+
trackEvent('Show preview', props.resource.id)
|
|
356
|
+
}
|
|
357
|
+
if (option.key === 'data-structure') {
|
|
358
|
+
trackEvent('Show data structure', props.resource.id)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
</script>
|
|
@@ -11,32 +11,15 @@
|
|
|
11
11
|
</AppLink>
|
|
12
12
|
</h3>
|
|
13
13
|
<div class="order-3 text-sm m-0 text-gray-medium">
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
v-if="organizationUrl"
|
|
21
|
-
class="link overflow-hidden"
|
|
22
|
-
:to="organizationUrl"
|
|
23
|
-
>
|
|
24
|
-
<OrganizationNameWithCertificate
|
|
25
|
-
:organization="reuse.organization"
|
|
26
|
-
/>
|
|
27
|
-
</AppLink>
|
|
28
|
-
<OrganizationNameWithCertificate
|
|
29
|
-
v-else
|
|
30
|
-
:organization="reuse.organization"
|
|
31
|
-
/>
|
|
32
|
-
</span>
|
|
33
|
-
<span
|
|
34
|
-
v-else-if="ownerName"
|
|
35
|
-
class="mr-1 truncate"
|
|
36
|
-
>{{ ownerName }}</span>
|
|
14
|
+
<div class="text-sm mb-0 flex items-center">
|
|
15
|
+
<ObjectCardOwner
|
|
16
|
+
:organization="reuse.organization"
|
|
17
|
+
:owner="reuse.owner"
|
|
18
|
+
:organization-url="organizationUrl"
|
|
19
|
+
/>
|
|
37
20
|
<RiSubtractLine class="size-4 flex-none fill-gray-medium" />
|
|
38
21
|
<span class="block flex-none">{{ t('publié {date}', { date: formatRelativeIfRecentDate(reuse.created_at, { dateStyle: 'medium' }) }) }}</span>
|
|
39
|
-
</
|
|
22
|
+
</div>
|
|
40
23
|
<ReuseDetails :reuse />
|
|
41
24
|
</div>
|
|
42
25
|
</div>
|
|
@@ -82,11 +65,10 @@ import { RiLockLine, RiSubtractLine } from '@remixicon/vue'
|
|
|
82
65
|
import { computed } from 'vue'
|
|
83
66
|
import type { RouteLocationRaw } from 'vue-router'
|
|
84
67
|
import { useFormatDate } from '../functions/dates'
|
|
85
|
-
import { getOwnerName } from '../functions/owned'
|
|
86
68
|
import type { Reuse } from '../types/reuses'
|
|
87
69
|
import { useTranslation } from '../composables/useTranslation'
|
|
88
70
|
import AppLink from './AppLink.vue'
|
|
89
|
-
import
|
|
71
|
+
import ObjectCardOwner from './ObjectCardOwner.vue'
|
|
90
72
|
import ReuseDetails from './ReuseDetails.vue'
|
|
91
73
|
import Placeholder from './Placeholder.vue'
|
|
92
74
|
|
|
@@ -109,7 +91,5 @@ const props = defineProps<{
|
|
|
109
91
|
const { t } = useTranslation()
|
|
110
92
|
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
111
93
|
|
|
112
|
-
const ownerName = computed(() => getOwnerName(props.reuse))
|
|
113
|
-
|
|
114
94
|
const reuseUrl = computed(() => props.reuseUrl || props.reuse.page)
|
|
115
95
|
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ObjectCard media-size="lg">
|
|
3
|
+
<template #badge>
|
|
4
|
+
<ObjectCardBadge
|
|
5
|
+
v-if="reuse.private"
|
|
6
|
+
:icon="RiLockLine"
|
|
7
|
+
>
|
|
8
|
+
{{ t('Brouillon') }}
|
|
9
|
+
</ObjectCardBadge>
|
|
10
|
+
<ObjectCardBadge
|
|
11
|
+
v-else-if="reuse.archived"
|
|
12
|
+
:icon="RiArchiveLine"
|
|
13
|
+
>
|
|
14
|
+
{{ t('Archivé') }}
|
|
15
|
+
</ObjectCardBadge>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<template #media>
|
|
19
|
+
<img
|
|
20
|
+
v-if="reuse.image"
|
|
21
|
+
:src="reuse.image"
|
|
22
|
+
class="w-full h-full object-cover"
|
|
23
|
+
:alt="reuse.title"
|
|
24
|
+
>
|
|
25
|
+
<Placeholder
|
|
26
|
+
v-else
|
|
27
|
+
type="Reuse"
|
|
28
|
+
class="w-full h-full"
|
|
29
|
+
/>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<ObjectCardHeader
|
|
33
|
+
:icon="RiLineChartLine"
|
|
34
|
+
:url="reuseUrl || reuse.page"
|
|
35
|
+
>
|
|
36
|
+
{{ reuse.title }}
|
|
37
|
+
</ObjectCardHeader>
|
|
38
|
+
|
|
39
|
+
<div
|
|
40
|
+
v-if="reuse.organization || reuse.owner"
|
|
41
|
+
class="text-sm m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
|
|
42
|
+
>
|
|
43
|
+
<ObjectCardOwner
|
|
44
|
+
:organization="reuse.organization"
|
|
45
|
+
:owner="reuse.owner"
|
|
46
|
+
:organization-url="organizationUrl"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="mx-0 -mb-1 flex flex-wrap items-center text-sm text-gray-medium mt-1">
|
|
51
|
+
<ReuseDetails :reuse />
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<ObjectCardShortDescription :text="reuse.description" />
|
|
55
|
+
|
|
56
|
+
<slot />
|
|
57
|
+
</ObjectCard>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script setup lang="ts">
|
|
61
|
+
import { RiArchiveLine, RiLineChartLine, RiLockLine } from '@remixicon/vue'
|
|
62
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
63
|
+
import type { Reuse } from '../types/reuses'
|
|
64
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
65
|
+
import ReuseDetails from './ReuseDetails.vue'
|
|
66
|
+
import Placeholder from './Placeholder.vue'
|
|
67
|
+
import ObjectCard from './ObjectCard.vue'
|
|
68
|
+
import ObjectCardHeader from './ObjectCardHeader.vue'
|
|
69
|
+
import ObjectCardOwner from './ObjectCardOwner.vue'
|
|
70
|
+
import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
|
|
71
|
+
import ObjectCardBadge from './ObjectCardBadge.vue'
|
|
72
|
+
|
|
73
|
+
defineProps<{
|
|
74
|
+
reuse: Reuse
|
|
75
|
+
reuseUrl?: RouteLocationRaw
|
|
76
|
+
organizationUrl?: RouteLocationRaw
|
|
77
|
+
}>()
|
|
78
|
+
|
|
79
|
+
const { t } = useTranslation()
|
|
80
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- [&_.fr-input-group]:!mb-0 disables DSFR margin-bottom since we use gap for spacing -->
|
|
3
|
+
<div class="flex flex-col gap-4 [&_.fr-input-group]:!mb-0">
|
|
4
|
+
<slot
|
|
5
|
+
:is-enabled="isBasicFilter"
|
|
6
|
+
:get-order="getBasicOrder"
|
|
7
|
+
/>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<Disclosure
|
|
11
|
+
v-if="advancedFilters.length"
|
|
12
|
+
v-slot="{ open }"
|
|
13
|
+
as="div"
|
|
14
|
+
class="mt-4"
|
|
15
|
+
>
|
|
16
|
+
<DisclosureButton class="flex w-[calc(100%+2rem)] items-center justify-between -mx-4 px-4 py-3 font-bold md:w-full md:mx-0 md:px-0 md:text-sm md:leading-tight md:mb-2">
|
|
17
|
+
{{ t('Filtres avancés') }}
|
|
18
|
+
<RiArrowDownSLine
|
|
19
|
+
class="size-4 transition-transform"
|
|
20
|
+
:class="{ 'rotate-180': open }"
|
|
21
|
+
/>
|
|
22
|
+
</DisclosureButton>
|
|
23
|
+
<DisclosurePanel class="flex flex-col gap-4 mt-4 [&_.fr-input-group]:!mb-0">
|
|
24
|
+
<slot
|
|
25
|
+
:is-enabled="isAdvancedFilter"
|
|
26
|
+
:get-order="getAdvancedOrder"
|
|
27
|
+
/>
|
|
28
|
+
</DisclosurePanel>
|
|
29
|
+
</Disclosure>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
|
34
|
+
import { RiArrowDownSLine } from '@remixicon/vue'
|
|
35
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
36
|
+
|
|
37
|
+
const props = defineProps<{
|
|
38
|
+
basicFilters: string[]
|
|
39
|
+
advancedFilters: string[]
|
|
40
|
+
}>()
|
|
41
|
+
|
|
42
|
+
const { t } = useTranslation()
|
|
43
|
+
|
|
44
|
+
const isBasicFilter = (name: string) => props.basicFilters.includes(name)
|
|
45
|
+
const isAdvancedFilter = (name: string) => props.advancedFilters.includes(name)
|
|
46
|
+
|
|
47
|
+
const getBasicOrder = (name: string) => props.basicFilters.indexOf(name)
|
|
48
|
+
const getAdvancedOrder = (name: string) => props.advancedFilters.indexOf(name)
|
|
49
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FilterButtonGroup
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
:options="options"
|
|
5
|
+
:label="t(`Modalités d'accès`)"
|
|
6
|
+
:all-label="t('Toutes')"
|
|
7
|
+
:facets="facets"
|
|
8
|
+
:loading="loading"
|
|
9
|
+
name="access_type"
|
|
10
|
+
highlight-active
|
|
11
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
12
|
+
/>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import type { FacetItem } from '../../../types/search'
|
|
17
|
+
import { useTranslation } from '../../../composables/useTranslation'
|
|
18
|
+
import FilterButtonGroup from './FilterButtonGroup.vue'
|
|
19
|
+
|
|
20
|
+
defineProps<{
|
|
21
|
+
modelValue: string | undefined
|
|
22
|
+
facets?: FacetItem[]
|
|
23
|
+
loading?: boolean
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits<{
|
|
27
|
+
'update:modelValue': [value: string | undefined]
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const { t } = useTranslation()
|
|
31
|
+
|
|
32
|
+
const options = [
|
|
33
|
+
{ value: 'open', label: t('Téléchargement libre') },
|
|
34
|
+
{ value: 'open_with_account', label: t('Ouvert sous condition') },
|
|
35
|
+
{ value: 'restricted', label: t('Accessible sous habilitation') },
|
|
36
|
+
]
|
|
37
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<FilterButtonGroup
|
|
3
|
+
:model-value="modelValue"
|
|
4
|
+
:options="options"
|
|
5
|
+
:label="t('Label de donnée')"
|
|
6
|
+
:all-label="t('Tous')"
|
|
7
|
+
:facets="facets"
|
|
8
|
+
:loading="loading"
|
|
9
|
+
name="badge"
|
|
10
|
+
highlight-active
|
|
11
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
12
|
+
/>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { computed } from 'vue'
|
|
17
|
+
import type { FacetItem } from '../../../types/search'
|
|
18
|
+
import { useFetch } from '../../../functions/api'
|
|
19
|
+
import { useTranslation } from '../../../composables/useTranslation'
|
|
20
|
+
import FilterButtonGroup from './FilterButtonGroup.vue'
|
|
21
|
+
|
|
22
|
+
defineProps<{
|
|
23
|
+
modelValue: string | undefined
|
|
24
|
+
facets?: FacetItem[]
|
|
25
|
+
loading?: boolean
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
'update:modelValue': [value: string | undefined]
|
|
30
|
+
}>()
|
|
31
|
+
|
|
32
|
+
const { t } = useTranslation()
|
|
33
|
+
|
|
34
|
+
const { data: badgesRecord } = await useFetch<Record<string, string>>('/api/1/datasets/badges/', { lazy: true })
|
|
35
|
+
|
|
36
|
+
const options = computed(() => {
|
|
37
|
+
if (!badgesRecord.value) return []
|
|
38
|
+
return Object.entries(badgesRecord.value).map(([value, label]) => ({ value, label }))
|
|
39
|
+
})
|
|
40
|
+
</script>
|