@datagouv/components-next 1.0.0 → 1.0.2-dev.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/assets/main.css +0 -28
- package/dist/{Control-BNCDn-8E.js → Control-DuZJdKV_.js} +1 -1
- package/dist/{Datafair.client-Dls5AHTE.js → Datafair.client-DpeBuzZZ.js} +2 -2
- package/dist/{Event-BOgJUhNR.js → Event--kp8kMdJ.js} +21 -21
- package/dist/{Image-BN-4XkIn.js → Image-34hvypZI.js} +114 -58
- package/dist/{JsonPreview.client-DPDTs433.js → JsonPreview.client-B3sQR-rW.js} +16 -16
- package/dist/{Map-BdT3i2C4.js → Map-BjUnLyj8.js} +1534 -1466
- package/dist/{MapContainer.client-BdAzd7bj.js → MapContainer.client-BA6GCqKX.js} +8 -8
- package/dist/{OSM-CamriM9b.js → OSM-s40W6sQ2.js} +22 -12
- package/dist/PdfPreview.client-CbeSChb8.js +14513 -0
- package/dist/Pmtiles.client-D8pFim1L.js +25190 -0
- package/dist/{ScaleLine-BiesrgOv.js → ScaleLine-KW-nXqp3.js} +2 -2
- package/dist/Swagger.client-B-2Q16qa.js +4 -0
- package/dist/{Tile-DCuqwNOI.js → Tile-DbNFNPfU.js} +189 -172
- package/dist/{TileImage-CmZf8EdU.js → TileImage-BsXBxMtq.js} +132 -102
- package/dist/{View-DcDc7N2K.js → View-BR92hTWP.js} +8 -8
- package/dist/{XmlPreview.client-C0OgBkSq.js → XmlPreview.client-DWJt61wG.js} +15 -15
- package/dist/{common-C4rDcQpp.js → common-PJfpC179.js} +34 -33
- package/dist/components-next.css +4 -4
- package/dist/components-next.js +130 -125
- package/dist/components.css +2 -2
- package/dist/{index-BRGqW8aQ.js → index-DVp7Y0Xu.js} +11105 -6743
- package/dist/{main-CNHxAJ8J.js → main-CrSRA2X-.js} +25208 -25053
- package/dist/{proj-CKwYjU38.js → proj-DsetBcW7.js} +513 -498
- package/dist/{tilecoord-YW3qEH_j.js → tilecoord-Db24Px13.js} +242 -224
- package/dist/{vue3-xml-viewer.common-CmAdQfIy.js → vue3-xml-viewer.common-BjA4LdSC.js} +1 -1
- package/package.json +3 -2
- package/src/components/DataserviceCard.vue +3 -3
- package/src/components/DatasetCard.vue +2 -2
- package/src/components/DatasetQuality.vue +23 -16
- package/src/components/DatasetQualityInline.vue +13 -17
- package/src/components/DatasetQualityScore.vue +12 -15
- package/src/components/DiscussionMessageCard.vue +1 -1
- package/src/components/ObjectCard.vue +2 -2
- package/src/components/ObjectCardHeader.vue +1 -1
- package/src/components/OrganizationHorizontalCard.vue +87 -0
- package/src/components/OrganizationNameWithCertificate.vue +1 -1
- package/src/components/ProgressBar.vue +31 -0
- package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
- package/src/components/ResourceAccordion/JsonPreview.client.vue +3 -3
- package/src/components/ResourceAccordion/MapContainer.client.vue +1 -1
- package/src/components/ResourceAccordion/PdfPreview.client.vue +70 -74
- package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
- package/src/components/ResourceAccordion/Preview.vue +1 -1
- package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -8
- package/src/components/ResourceAccordion/XmlPreview.client.vue +3 -3
- package/src/components/ReuseHorizontalCard.vue +1 -1
- package/src/components/Search/Filter/ProducerTypeFilter.vue +13 -3
- package/src/components/Search/GlobalSearch.vue +124 -28
- package/src/components/Toggletip.vue +5 -2
- package/src/components/TopicCard.vue +1 -1
- package/src/composables/useHasTabularData.ts +15 -0
- package/src/composables/useResourceCapabilities.ts +3 -4
- package/src/composables/useTranslation.ts +2 -1
- package/src/functions/api.types.ts +1 -1
- package/src/main.ts +8 -1
- package/src/types/search.ts +29 -1
- package/dist/PdfPreview.client-CopqSDyt.js +0 -107
- package/dist/Pmtiles.client-mF6xaOO_.js +0 -22812
- package/dist/Swagger.client-eJ7gpfZA.js +0 -4
- package/dist/pdf-vue3-IkJO65RH.js +0 -273
- package/dist/pdf.min-f72cfa08-CdgJTooZ.js +0 -9501
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
rel="ugc nofollow noopener"
|
|
114
114
|
new-tab
|
|
115
115
|
size="xs"
|
|
116
|
+
color="secondary"
|
|
116
117
|
external
|
|
117
118
|
@click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
|
|
118
119
|
>
|
|
@@ -127,7 +128,7 @@
|
|
|
127
128
|
:id="resource.id + '-copy'"
|
|
128
129
|
:data-clipboard-text="resource.url"
|
|
129
130
|
:aria-describedby="resourceTitleId"
|
|
130
|
-
color="
|
|
131
|
+
color="secondary"
|
|
131
132
|
size="xs"
|
|
132
133
|
:icon="RiFileCopyLine"
|
|
133
134
|
>
|
|
@@ -390,6 +391,7 @@ import { getResourceFormatIcon, getResourceTitleId, detectOgcService } from '../
|
|
|
390
391
|
import BrandedButton from '../BrandedButton.vue'
|
|
391
392
|
import { getResourceExternalUrl, getResourceFilesize } from '../../functions/datasets'
|
|
392
393
|
import { useTranslation } from '../../composables/useTranslation'
|
|
394
|
+
import { useHasTabularData } from '../../composables/useHasTabularData'
|
|
393
395
|
import Metadata from './Metadata.vue'
|
|
394
396
|
import SchemaBadge from './SchemaBadge.vue'
|
|
395
397
|
import ResourceIcon from './ResourceIcon.vue'
|
|
@@ -426,6 +428,7 @@ const DatafairPreview = defineAsyncComponent(() => import('./Datafair.client.vue
|
|
|
426
428
|
|
|
427
429
|
const { t } = useTranslation()
|
|
428
430
|
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
431
|
+
const checkTabularData = useHasTabularData()
|
|
429
432
|
|
|
430
433
|
const hasPreview = computed(() => {
|
|
431
434
|
// For JSON, PDF, and XML files, show preview.
|
|
@@ -435,13 +438,7 @@ const hasPreview = computed(() => {
|
|
|
435
438
|
return format === 'json' || format === 'pdf' || format === 'xml'
|
|
436
439
|
})
|
|
437
440
|
|
|
438
|
-
const hasTabularData = computed(() =>
|
|
439
|
-
// Determines if we should show the "Données" tab for tabular files AND the "Structure des données" tab (for tabular data structure)
|
|
440
|
-
return config.tabularApiUrl
|
|
441
|
-
&& props.resource.extras['analysis:parsing:parsing_table']
|
|
442
|
-
&& !props.resource.extras['analysis:parsing:error']
|
|
443
|
-
&& (config.tabularAllowRemote || props.resource.filetype === 'file')
|
|
444
|
-
})
|
|
441
|
+
const hasTabularData = computed(() => checkTabularData(props.resource))
|
|
445
442
|
|
|
446
443
|
const hasPmtiles = computed(() => {
|
|
447
444
|
return props.resource.extras['analysis:parsing:pmtiles_url'] || props.resource.format === 'pmtiles'
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
type="warning"
|
|
15
15
|
class="flex items-center space-x-2"
|
|
16
16
|
>
|
|
17
|
-
<RiErrorWarningLine class="
|
|
17
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
18
18
|
<span>{{ fileSizeBytes
|
|
19
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
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.")
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
type="warning"
|
|
26
26
|
class="flex items-center space-x-2"
|
|
27
27
|
>
|
|
28
|
-
<RiErrorWarningLine class="
|
|
28
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
29
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
30
|
</SimpleBanner>
|
|
31
31
|
<SimpleBanner
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
type="warning"
|
|
34
34
|
class="flex items-center space-x-2"
|
|
35
35
|
>
|
|
36
|
-
<RiErrorWarningLine class="
|
|
36
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
37
37
|
<span>{{ t("Erreur lors du chargement de l'aperçu XML.") }}</span>
|
|
38
38
|
</SimpleBanner>
|
|
39
39
|
</div>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
<div
|
|
40
40
|
v-if="reuse.organization || reuse.owner"
|
|
41
|
-
class="text-sm
|
|
41
|
+
class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
|
|
42
42
|
>
|
|
43
43
|
<ObjectCardOwner
|
|
44
44
|
:organization="reuse.organization"
|
|
@@ -13,15 +13,19 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
|
|
15
15
|
<script setup lang="ts">
|
|
16
|
+
import { computed } from 'vue'
|
|
16
17
|
import type { FacetItem } from '../../../types/search'
|
|
17
18
|
import { useTranslation } from '../../../composables/useTranslation'
|
|
18
19
|
import FilterButtonGroup from './FilterButtonGroup.vue'
|
|
19
20
|
|
|
20
|
-
defineProps<{
|
|
21
|
+
const props = withDefaults(defineProps<{
|
|
21
22
|
modelValue: string | undefined
|
|
22
23
|
facets?: FacetItem[]
|
|
23
24
|
loading?: boolean
|
|
24
|
-
|
|
25
|
+
exclude?: string[]
|
|
26
|
+
}>(), {
|
|
27
|
+
exclude: () => [],
|
|
28
|
+
})
|
|
25
29
|
|
|
26
30
|
const emit = defineEmits<{
|
|
27
31
|
'update:modelValue': [value: string | undefined]
|
|
@@ -29,11 +33,17 @@ const emit = defineEmits<{
|
|
|
29
33
|
|
|
30
34
|
const { t } = useTranslation()
|
|
31
35
|
|
|
32
|
-
const
|
|
36
|
+
const allOptions = [
|
|
33
37
|
{ value: 'public-service', label: t('Service public') },
|
|
34
38
|
{ value: 'local-authority', label: t('Collectivité territoriale') },
|
|
35
39
|
{ value: 'company', label: t('Entreprise') },
|
|
36
40
|
{ value: 'association', label: t('Association') },
|
|
37
41
|
{ value: 'user', label: t('Utilisateur') },
|
|
38
42
|
]
|
|
43
|
+
|
|
44
|
+
const options = computed(() =>
|
|
45
|
+
props.exclude.length > 0
|
|
46
|
+
? allOptions.filter(o => !props.exclude.includes(o.value))
|
|
47
|
+
: allOptions,
|
|
48
|
+
)
|
|
39
49
|
</script>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
>
|
|
12
12
|
<SearchInput
|
|
13
13
|
v-model="q"
|
|
14
|
-
:placeholder="placeholder ||
|
|
14
|
+
:placeholder="placeholder || typesMeta[currentType].placeholder"
|
|
15
15
|
/>
|
|
16
16
|
</div>
|
|
17
17
|
<div class="grid grid-cols-12 mt-2 md:mt-5">
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
v-model="producerType"
|
|
124
124
|
:facets="getFacets('producer_type')"
|
|
125
125
|
:loading="searchResultsStatus === 'pending'"
|
|
126
|
+
:exclude="currentType === 'organizations' ? ['user'] : []"
|
|
126
127
|
:style="{ order: getOrder('producer_type') }"
|
|
127
128
|
/>
|
|
128
129
|
<DatasetBadgeFilter
|
|
@@ -177,31 +178,44 @@
|
|
|
177
178
|
>
|
|
178
179
|
{{ t("{count} résultats | {count} résultat | {count} résultats", searchResults.total) }}
|
|
179
180
|
</p>
|
|
180
|
-
<div class="fr-col-auto fr-grid-row fr-grid-row--middle">
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
{{ t('Trier par :') }}
|
|
186
|
-
</label>
|
|
187
|
-
<div class="fr-col">
|
|
188
|
-
<select
|
|
189
|
-
id="sort-search"
|
|
190
|
-
v-model="sort"
|
|
191
|
-
class="fr-select text-sm shadow-input-blue!"
|
|
181
|
+
<div class="fr-col-auto fr-grid-row fr-grid-row--middle gap-4">
|
|
182
|
+
<div class="flex items-center">
|
|
183
|
+
<label
|
|
184
|
+
for="sort-search"
|
|
185
|
+
class="fr-col-auto text-sm m-0 mr-2"
|
|
192
186
|
>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
187
|
+
{{ t('Trier par :') }}
|
|
188
|
+
</label>
|
|
189
|
+
<div class="fr-col">
|
|
190
|
+
<select
|
|
191
|
+
id="sort-search"
|
|
192
|
+
v-model="sort"
|
|
193
|
+
class="fr-select text-sm shadow-input-blue!"
|
|
200
194
|
>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
195
|
+
<option :value="undefined">
|
|
196
|
+
{{ t('Pertinence') }}
|
|
197
|
+
</option>
|
|
198
|
+
<option
|
|
199
|
+
v-for="option in allSortOptions"
|
|
200
|
+
:key="option.value"
|
|
201
|
+
:value="option.value"
|
|
202
|
+
:hidden="!activeSortValues.has(option.value)"
|
|
203
|
+
>
|
|
204
|
+
{{ option.label }}
|
|
205
|
+
</option>
|
|
206
|
+
</select>
|
|
207
|
+
</div>
|
|
204
208
|
</div>
|
|
209
|
+
<BrandedButton
|
|
210
|
+
v-if="rssUrl"
|
|
211
|
+
:href="rssUrl"
|
|
212
|
+
:title="t('Flux RSS')"
|
|
213
|
+
color="secondary"
|
|
214
|
+
size="sm"
|
|
215
|
+
:icon="RiRssLine"
|
|
216
|
+
icon-only
|
|
217
|
+
target="_blank"
|
|
218
|
+
/>
|
|
205
219
|
</div>
|
|
206
220
|
</div>
|
|
207
221
|
<transition mode="out-in">
|
|
@@ -241,6 +255,14 @@
|
|
|
241
255
|
<ReuseHorizontalCard :reuse="(result as Reuse)" />
|
|
242
256
|
</slot>
|
|
243
257
|
</template>
|
|
258
|
+
<template v-else-if="currentType === 'organizations'">
|
|
259
|
+
<slot
|
|
260
|
+
name="organization"
|
|
261
|
+
:organization="result"
|
|
262
|
+
>
|
|
263
|
+
<OrganizationHorizontalCard :organization="(result as Organization)" />
|
|
264
|
+
</slot>
|
|
265
|
+
</template>
|
|
244
266
|
</li>
|
|
245
267
|
</ul>
|
|
246
268
|
<Pagination
|
|
@@ -294,6 +316,7 @@
|
|
|
294
316
|
color="tertiary"
|
|
295
317
|
:href="componentsConfig.forumUrl"
|
|
296
318
|
:icon="RiLightbulbLine"
|
|
319
|
+
keep-margins-even-without-borders
|
|
297
320
|
>
|
|
298
321
|
{{ t("Voir le forum") }}
|
|
299
322
|
</BrandedButton>
|
|
@@ -312,7 +335,7 @@
|
|
|
312
335
|
<script setup lang="ts">
|
|
313
336
|
import { computed, watch, useTemplateRef, type Ref } from 'vue'
|
|
314
337
|
import { useRouteQuery } from '@vueuse/router'
|
|
315
|
-
import { RiCloseCircleLine, RiDatabase2Line,
|
|
338
|
+
import { RiBuilding2Line, RiCloseCircleLine, RiDatabase2Line, RiLightbulbLine, RiLineChartLine, RiRssLine, RiTerminalLine } from '@remixicon/vue'
|
|
316
339
|
import magnifyingGlassSrc from '../../../assets/illustrations/magnifying_glass.svg?url'
|
|
317
340
|
import { useTranslation } from '../../composables/useTranslation'
|
|
318
341
|
import { useDebouncedRef } from '../../composables/useDebouncedRef'
|
|
@@ -322,8 +345,9 @@ import { useFetch } from '../../functions/api'
|
|
|
322
345
|
import { getLink } from '../../functions/pagination'
|
|
323
346
|
import type { Dataset } from '../../types/datasets'
|
|
324
347
|
import type { Dataservice } from '../../types/dataservices'
|
|
348
|
+
import type { Organization } from '../../types/organizations'
|
|
325
349
|
import type { Reuse } from '../../types/reuses'
|
|
326
|
-
import type { GlobalSearchConfig, SearchType, DatasetSearchResponse, DataserviceSearchResponse, ReuseSearchResponse, FacetItem } from '../../types/search'
|
|
350
|
+
import type { GlobalSearchConfig, SearchType, SortOption, DatasetSearchResponse, DataserviceSearchResponse, ReuseSearchResponse, OrganizationSearchResponse, FacetItem } from '../../types/search'
|
|
327
351
|
import { getDefaultGlobalSearchConfig } from '../../types/search'
|
|
328
352
|
import BrandedButton from '../BrandedButton.vue'
|
|
329
353
|
import LoadingBlock from '../LoadingBlock.vue'
|
|
@@ -332,6 +356,7 @@ import RadioGroup from '../RadioGroup.vue'
|
|
|
332
356
|
import RadioInput from '../RadioInput.vue'
|
|
333
357
|
import DatasetCard from '../DatasetCard.vue'
|
|
334
358
|
import DataserviceCard from '../DataserviceCard.vue'
|
|
359
|
+
import OrganizationHorizontalCard from '../OrganizationHorizontalCard.vue'
|
|
335
360
|
import ReuseHorizontalCard from '../ReuseHorizontalCard.vue'
|
|
336
361
|
import SearchInput from './SearchInput.vue'
|
|
337
362
|
import Sidemenu from './Sidemenu.vue'
|
|
@@ -385,6 +410,22 @@ const activeSortOptions = computed(() =>
|
|
|
385
410
|
currentTypeConfig.value?.sortOptions ?? [],
|
|
386
411
|
)
|
|
387
412
|
|
|
413
|
+
const activeSortValues = computed(() =>
|
|
414
|
+
new Set(activeSortOptions.value.map(o => o.value as string)),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
// Deduplicated union of all sort options across all search types.
|
|
418
|
+
// Rendered as hidden <option> elements so the <select> always has a stable
|
|
419
|
+
// intrinsic width regardless of which type is currently active.
|
|
420
|
+
const allSortOptions = computed(() => {
|
|
421
|
+
const seen = new Set<string>()
|
|
422
|
+
return props.config.flatMap(c => (c.sortOptions ?? []) as SortOption<string>[]).filter((o) => {
|
|
423
|
+
if (seen.has(o.value)) return false
|
|
424
|
+
seen.add(o.value)
|
|
425
|
+
return true
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
388
429
|
const activeFilters = computed(() => [
|
|
389
430
|
...(currentTypeConfig.value?.basicFilters ?? []),
|
|
390
431
|
...(currentTypeConfig.value?.advancedFilters ?? []),
|
|
@@ -458,6 +499,7 @@ watch(currentType, () => {
|
|
|
458
499
|
const datasetsEnabled = computed(() => props.config.some(c => c.class === 'datasets'))
|
|
459
500
|
const dataservicesEnabled = computed(() => props.config.some(c => c.class === 'dataservices'))
|
|
460
501
|
const reusesEnabled = computed(() => props.config.some(c => c.class === 'reuses'))
|
|
502
|
+
const organizationsEnabled = computed(() => props.config.some(c => c.class === 'organizations'))
|
|
461
503
|
|
|
462
504
|
// Create stable params for each type
|
|
463
505
|
const stableParamsOptions = {
|
|
@@ -480,11 +522,16 @@ const reusesParams = useStableQueryParams({
|
|
|
480
522
|
...stableParamsOptions,
|
|
481
523
|
typeConfig: props.config.find(c => c.class === 'reuses'),
|
|
482
524
|
})
|
|
525
|
+
const organizationsParams = useStableQueryParams({
|
|
526
|
+
...stableParamsOptions,
|
|
527
|
+
typeConfig: props.config.find(c => c.class === 'organizations'),
|
|
528
|
+
})
|
|
483
529
|
|
|
484
530
|
// URLs that return null when type is not enabled
|
|
485
531
|
const datasetsUrl = computed(() => datasetsEnabled.value ? '/api/2/datasets/search/' : null)
|
|
486
532
|
const dataservicesUrl = computed(() => dataservicesEnabled.value ? '/api/2/dataservices/search/' : null)
|
|
487
533
|
const reusesUrl = computed(() => reusesEnabled.value ? '/api/2/reuses/search/' : null)
|
|
534
|
+
const organizationsUrl = computed(() => organizationsEnabled.value ? '/api/2/organizations/search/' : null)
|
|
488
535
|
|
|
489
536
|
// Reset page on filter/sort change
|
|
490
537
|
const filtersForReset = computed(() => ({
|
|
@@ -528,7 +575,7 @@ const hasFilters = computed(() => {
|
|
|
528
575
|
|| reuseType.value
|
|
529
576
|
})
|
|
530
577
|
|
|
531
|
-
const showForumLink = computed(() => currentType.value === 'datasets' && !!componentsConfig.forumUrl)
|
|
578
|
+
const showForumLink = computed(() => (currentType.value === 'datasets' || currentType.value === 'dataservices') && !!componentsConfig.forumUrl)
|
|
532
579
|
|
|
533
580
|
function resetFilters() {
|
|
534
581
|
organizationId.value = undefined
|
|
@@ -564,31 +611,80 @@ const { data: reusesResults, status: reusesStatus } = await useFetch<ReuseSearch
|
|
|
564
611
|
reusesUrl,
|
|
565
612
|
{ params: reusesParams, lazy: true, server: initialType === 'reuses' },
|
|
566
613
|
)
|
|
614
|
+
const { data: organizationsResults, status: organizationsStatus } = await useFetch<OrganizationSearchResponse<Organization>>(
|
|
615
|
+
organizationsUrl,
|
|
616
|
+
{ params: organizationsParams, lazy: true, server: initialType === 'organizations' },
|
|
617
|
+
)
|
|
567
618
|
|
|
568
619
|
const typesMeta = {
|
|
569
620
|
datasets: {
|
|
570
621
|
icon: RiDatabase2Line,
|
|
571
622
|
name: t('Jeux de données'),
|
|
623
|
+
placeholder: t('ex. élections présidentielles'),
|
|
572
624
|
results: datasetsResults,
|
|
573
625
|
status: datasetsStatus,
|
|
574
626
|
},
|
|
575
627
|
dataservices: {
|
|
576
|
-
icon:
|
|
577
|
-
name: t('
|
|
628
|
+
icon: RiTerminalLine,
|
|
629
|
+
name: t('API'),
|
|
630
|
+
placeholder: t('ex: SIRENE'),
|
|
578
631
|
results: dataservicesResults,
|
|
579
632
|
status: dataservicesStatus,
|
|
580
633
|
},
|
|
581
634
|
reuses: {
|
|
582
635
|
icon: RiLineChartLine,
|
|
583
636
|
name: t('Réutilisations'),
|
|
637
|
+
placeholder: t('Rechercher une réutilisation de données'),
|
|
584
638
|
results: reusesResults,
|
|
585
639
|
status: reusesStatus,
|
|
586
640
|
},
|
|
641
|
+
organizations: {
|
|
642
|
+
icon: RiBuilding2Line,
|
|
643
|
+
name: t('Organisations'),
|
|
644
|
+
placeholder: t('Rechercher une organisation'),
|
|
645
|
+
results: organizationsResults,
|
|
646
|
+
status: organizationsStatus,
|
|
647
|
+
},
|
|
587
648
|
} as const
|
|
588
649
|
|
|
589
650
|
const searchResults = computed(() => typesMeta[currentType.value].results.value)
|
|
590
651
|
const searchResultsStatus = computed(() => typesMeta[currentType.value].status.value)
|
|
591
652
|
|
|
653
|
+
// RSS feed URL for datasets
|
|
654
|
+
const rssUrl = computed(() => {
|
|
655
|
+
if (currentType.value !== 'datasets') return null
|
|
656
|
+
|
|
657
|
+
const params = new URLSearchParams()
|
|
658
|
+
const datasetsConfig = props.config.find(c => c.class === 'datasets')
|
|
659
|
+
|
|
660
|
+
// Add hidden filters first
|
|
661
|
+
if (datasetsConfig?.hiddenFilters) {
|
|
662
|
+
for (const hf of datasetsConfig.hiddenFilters) {
|
|
663
|
+
if (hf?.value) params.set(hf.key as string, String(hf.value))
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Add active filters
|
|
668
|
+
if (qDebounced.value) params.set('q', qDebounced.value)
|
|
669
|
+
if (organizationId.value) params.set('organization', organizationId.value)
|
|
670
|
+
if (organizationType.value) params.set('organization_badge', organizationType.value)
|
|
671
|
+
if (tag.value) params.set('tag', tag.value)
|
|
672
|
+
if (format.value) params.set('format', format.value)
|
|
673
|
+
if (license.value) params.set('license', license.value)
|
|
674
|
+
if (schema.value) params.set('schema', schema.value)
|
|
675
|
+
if (geozone.value) params.set('geozone', geozone.value)
|
|
676
|
+
if (granularity.value) params.set('granularity', granularity.value)
|
|
677
|
+
if (badge.value) params.set('badge', badge.value)
|
|
678
|
+
if (topic.value) params.set('topic', topic.value)
|
|
679
|
+
|
|
680
|
+
// Add sort if set
|
|
681
|
+
if (sort.value) params.set('sort', sort.value)
|
|
682
|
+
|
|
683
|
+
const queryString = params.toString()
|
|
684
|
+
const basePath = '/api/1/datasets/recent.atom'
|
|
685
|
+
return `${componentsConfig.apiBase}${basePath}${queryString ? '?' + queryString : ''}`
|
|
686
|
+
})
|
|
687
|
+
|
|
592
688
|
// Facets for filters
|
|
593
689
|
const currentFacets = computed(() => searchResults.value?.facets)
|
|
594
690
|
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
/>
|
|
14
14
|
<PopoverButton
|
|
15
15
|
v-bind="buttonProps"
|
|
16
|
-
class="
|
|
17
|
-
|
|
16
|
+
:class="[
|
|
17
|
+
buttonClass ?? 'border-transparent -outline-offset-2 inline-flex items-center justify-center hover:bg-gray-some',
|
|
18
|
+
{ 'w-8 h-8 rounded-full bg-transparent': styledButton && !buttonClass },
|
|
19
|
+
]"
|
|
18
20
|
>
|
|
19
21
|
<slot>
|
|
20
22
|
<RiInformationLine
|
|
@@ -57,6 +59,7 @@ import ValueWatcher from './ValueWatcher.vue'
|
|
|
57
59
|
|
|
58
60
|
withDefaults(defineProps<{
|
|
59
61
|
buttonProps?: object
|
|
62
|
+
buttonClass?: string
|
|
60
63
|
noMargin?: boolean
|
|
61
64
|
styledButton?: boolean
|
|
62
65
|
}>(), {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
<div
|
|
32
32
|
v-if="topic.organization || topic.owner"
|
|
33
|
-
class="text-sm
|
|
33
|
+
class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
|
|
34
34
|
>
|
|
35
35
|
<ObjectCardOwner
|
|
36
36
|
:organization="topic.organization"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useComponentsConfig } from '../config'
|
|
2
|
+
import type { Resource } from '../types/resources'
|
|
3
|
+
|
|
4
|
+
export const useHasTabularData = () => {
|
|
5
|
+
const config = useComponentsConfig()
|
|
6
|
+
|
|
7
|
+
return (resource: Resource) => {
|
|
8
|
+
return (
|
|
9
|
+
config.tabularApiUrl
|
|
10
|
+
&& resource.extras['analysis:parsing:parsing_table']
|
|
11
|
+
&& !resource.extras['analysis:parsing:error']
|
|
12
|
+
&& (config.tabularAllowRemote || resource.filetype === 'file')
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, toValue, type MaybeRefOrGetter } from 'vue'
|
|
2
2
|
import { useComponentsConfig } from '../config'
|
|
3
3
|
import { useTranslation } from './useTranslation'
|
|
4
|
+
import { useHasTabularData } from './useHasTabularData'
|
|
4
5
|
import { detectOgcService } from '../functions/resources'
|
|
5
6
|
import { isOrganizationCertified } from '../functions/organizations'
|
|
6
7
|
import type { Resource } from '../types/resources'
|
|
@@ -15,6 +16,7 @@ export function useResourceCapabilities(
|
|
|
15
16
|
) {
|
|
16
17
|
const config = useComponentsConfig()
|
|
17
18
|
const { t } = useTranslation()
|
|
19
|
+
const checkTabularData = useHasTabularData()
|
|
18
20
|
|
|
19
21
|
const hasPreview = computed(() => {
|
|
20
22
|
const format = toValue(resource).format?.toLowerCase()
|
|
@@ -23,10 +25,7 @@ export function useResourceCapabilities(
|
|
|
23
25
|
|
|
24
26
|
const hasTabularData = computed(() => {
|
|
25
27
|
const r = toValue(resource)
|
|
26
|
-
return
|
|
27
|
-
&& r.extras['analysis:parsing:parsing_table']
|
|
28
|
-
&& !r.extras['analysis:parsing:error']
|
|
29
|
-
&& (config.tabularAllowRemote || r.filetype === 'file')
|
|
28
|
+
return checkTabularData(r)
|
|
30
29
|
})
|
|
31
30
|
|
|
32
31
|
const hasPmtiles = computed(() => {
|
|
@@ -21,7 +21,8 @@ function detectLanguage(): string {
|
|
|
21
21
|
const acceptLanguage = header
|
|
22
22
|
if (acceptLanguage) {
|
|
23
23
|
const primaryLang = acceptLanguage.split(';')[0]!.split(',')[0]!.split('-')[0]!.toLowerCase()
|
|
24
|
-
|
|
24
|
+
// Ignore wildcard * language, that should fallback to client side detection or default language
|
|
25
|
+
if (primaryLang !== '*') return primaryLang
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
catch {
|
|
@@ -35,5 +35,5 @@ export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
|
|
35
35
|
|
|
36
36
|
export type UseFetchFunction = (<DataT, ErrorT>(
|
|
37
37
|
url: string | Request | Ref<string | Request> | ComputedRef<string | null> | (() => string | Request),
|
|
38
|
-
options?: UseFetchOptions<DataT
|
|
38
|
+
options?: UseFetchOptions<DataT>,
|
|
39
39
|
) => Promise<AsyncData<DataT, ErrorT>>)
|
package/src/main.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type { Weight, WellType } from './types/ui'
|
|
|
24
24
|
import type { User, UserReference } from './types/users'
|
|
25
25
|
import type { Report, ReportSubject, ReportReason } from './types/reports'
|
|
26
26
|
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
|
|
27
|
-
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions } from './types/search'
|
|
27
|
+
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultOrganizationConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions, defaultOrganizationSortOptions } from './types/search'
|
|
28
28
|
|
|
29
29
|
import ActivityList from './components/ActivityList/ActivityList.vue'
|
|
30
30
|
import UserActivityList from './components/ActivityList/UserActivityList.vue'
|
|
@@ -49,12 +49,14 @@ import DatasetQuality from './components/DatasetQuality.vue'
|
|
|
49
49
|
import DatasetQualityInline from './components/DatasetQualityInline.vue'
|
|
50
50
|
import DatasetQualityItem from './components/DatasetQualityItem.vue'
|
|
51
51
|
import DatasetQualityScore from './components/DatasetQualityScore.vue'
|
|
52
|
+
import ProgressBar from './components/ProgressBar.vue'
|
|
52
53
|
import DatasetQualityTooltipContent from './components/DatasetQualityTooltipContent.vue'
|
|
53
54
|
import ExtraAccordion from './components/ExtraAccordion.vue'
|
|
54
55
|
import LabelTag from './components/DatasetLabelTag.vue'
|
|
55
56
|
import LoadingBlock from './components/LoadingBlock.vue'
|
|
56
57
|
import MarkdownViewer from './components/MarkdownViewer.vue'
|
|
57
58
|
import OrganizationCard from './components/OrganizationCard.vue'
|
|
59
|
+
import OrganizationHorizontalCard from './components/OrganizationHorizontalCard.vue'
|
|
58
60
|
import OrganizationLogo from './components/OrganizationLogo.vue'
|
|
59
61
|
import OrganizationNameWithCertificate from './components/OrganizationNameWithCertificate.vue'
|
|
60
62
|
import OwnerType from './components/OwnerType.vue'
|
|
@@ -101,6 +103,7 @@ export * from './composables/useActiveDescendant'
|
|
|
101
103
|
export * from './composables/useMetrics'
|
|
102
104
|
export * from './composables/useReuseType'
|
|
103
105
|
export * from './composables/useTranslation'
|
|
106
|
+
export * from './composables/useHasTabularData'
|
|
104
107
|
|
|
105
108
|
export * from './functions/activities'
|
|
106
109
|
export * from './functions/datasets'
|
|
@@ -221,10 +224,12 @@ export {
|
|
|
221
224
|
getDefaultDatasetConfig,
|
|
222
225
|
getDefaultDataserviceConfig,
|
|
223
226
|
getDefaultReuseConfig,
|
|
227
|
+
getDefaultOrganizationConfig,
|
|
224
228
|
getDefaultGlobalSearchConfig,
|
|
225
229
|
defaultDatasetSortOptions,
|
|
226
230
|
defaultDataserviceSortOptions,
|
|
227
231
|
defaultReuseSortOptions,
|
|
232
|
+
defaultOrganizationSortOptions,
|
|
228
233
|
}
|
|
229
234
|
|
|
230
235
|
// Vue Plugin
|
|
@@ -273,6 +278,7 @@ export {
|
|
|
273
278
|
Tag,
|
|
274
279
|
MarkdownViewer,
|
|
275
280
|
OrganizationCard,
|
|
281
|
+
OrganizationHorizontalCard,
|
|
276
282
|
OrganizationLogo,
|
|
277
283
|
OrganizationNameWithCertificate,
|
|
278
284
|
OwnerType,
|
|
@@ -280,6 +286,7 @@ export {
|
|
|
280
286
|
PaddedContainer,
|
|
281
287
|
Pagination,
|
|
282
288
|
Placeholder,
|
|
289
|
+
ProgressBar,
|
|
283
290
|
PostCard,
|
|
284
291
|
RadioGroup,
|
|
285
292
|
RadioInput,
|
package/src/types/search.ts
CHANGED
|
@@ -201,6 +201,7 @@ export type OrganizationSearchSort = 'reuses' | 'datasets' | 'followers' | 'view
|
|
|
201
201
|
|
|
202
202
|
export type OrganizationSearchFilters = {
|
|
203
203
|
badge?: OrganizationBadgeFilter
|
|
204
|
+
producer_type?: ProducerType
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
export type OrganizationSearchQueryParams = BaseSearchQueryParams<OrganizationSearchSort> & OrganizationSearchFilters
|
|
@@ -315,7 +316,16 @@ export type ReuseSearchConfig = {
|
|
|
315
316
|
sortOptions?: SortOption<ReuseSearchSort>[]
|
|
316
317
|
}
|
|
317
318
|
|
|
318
|
-
export type
|
|
319
|
+
export type OrganizationSearchConfig = {
|
|
320
|
+
class: 'organizations'
|
|
321
|
+
name?: string
|
|
322
|
+
hiddenFilters?: HiddenFilter<OrganizationSearchFilters>[]
|
|
323
|
+
basicFilters?: (keyof OrganizationSearchFilters)[]
|
|
324
|
+
advancedFilters?: (keyof OrganizationSearchFilters)[]
|
|
325
|
+
sortOptions?: SortOption<OrganizationSearchSort>[]
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export type SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig | OrganizationSearchConfig
|
|
319
329
|
|
|
320
330
|
export type SearchType = SearchTypeConfig['class']
|
|
321
331
|
|
|
@@ -340,6 +350,13 @@ export const defaultReuseSortOptions: SortOption<ReuseSearchSort>[] = [
|
|
|
340
350
|
{ value: '-datasets', label: 'Nombre de jeux de données' },
|
|
341
351
|
]
|
|
342
352
|
|
|
353
|
+
export const defaultOrganizationSortOptions: SortOption<OrganizationSearchSort>[] = [
|
|
354
|
+
{ value: '-created', label: 'Date de création' },
|
|
355
|
+
{ value: '-followers', label: `Nombre d'abonnés` },
|
|
356
|
+
{ value: '-datasets', label: 'Nombre de jeux de données' },
|
|
357
|
+
{ value: '-reuses', label: 'Nombre de réutilisations' },
|
|
358
|
+
]
|
|
359
|
+
|
|
343
360
|
export function getDefaultDatasetConfig(overrides?: Partial<Omit<DatasetSearchConfig, 'class'>>): DatasetSearchConfig {
|
|
344
361
|
return {
|
|
345
362
|
class: 'datasets',
|
|
@@ -370,10 +387,21 @@ export function getDefaultReuseConfig(overrides?: Partial<Omit<ReuseSearchConfig
|
|
|
370
387
|
}
|
|
371
388
|
}
|
|
372
389
|
|
|
390
|
+
export function getDefaultOrganizationConfig(overrides?: Partial<Omit<OrganizationSearchConfig, 'class'>>): OrganizationSearchConfig {
|
|
391
|
+
return {
|
|
392
|
+
class: 'organizations',
|
|
393
|
+
basicFilters: ['producer_type'],
|
|
394
|
+
advancedFilters: [],
|
|
395
|
+
sortOptions: defaultOrganizationSortOptions,
|
|
396
|
+
...overrides,
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
373
400
|
export function getDefaultGlobalSearchConfig(): GlobalSearchConfig {
|
|
374
401
|
return [
|
|
375
402
|
getDefaultDatasetConfig(),
|
|
376
403
|
getDefaultDataserviceConfig(),
|
|
377
404
|
getDefaultReuseConfig(),
|
|
405
|
+
getDefaultOrganizationConfig(),
|
|
378
406
|
]
|
|
379
407
|
}
|