@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.
Files changed (62) hide show
  1. package/assets/main.css +0 -28
  2. package/dist/{Control-BNCDn-8E.js → Control-DuZJdKV_.js} +1 -1
  3. package/dist/{Datafair.client-Dls5AHTE.js → Datafair.client-DpeBuzZZ.js} +2 -2
  4. package/dist/{Event-BOgJUhNR.js → Event--kp8kMdJ.js} +21 -21
  5. package/dist/{Image-BN-4XkIn.js → Image-34hvypZI.js} +114 -58
  6. package/dist/{JsonPreview.client-DPDTs433.js → JsonPreview.client-B3sQR-rW.js} +16 -16
  7. package/dist/{Map-BdT3i2C4.js → Map-BjUnLyj8.js} +1534 -1466
  8. package/dist/{MapContainer.client-BdAzd7bj.js → MapContainer.client-BA6GCqKX.js} +8 -8
  9. package/dist/{OSM-CamriM9b.js → OSM-s40W6sQ2.js} +22 -12
  10. package/dist/PdfPreview.client-CbeSChb8.js +14513 -0
  11. package/dist/Pmtiles.client-D8pFim1L.js +25190 -0
  12. package/dist/{ScaleLine-BiesrgOv.js → ScaleLine-KW-nXqp3.js} +2 -2
  13. package/dist/Swagger.client-B-2Q16qa.js +4 -0
  14. package/dist/{Tile-DCuqwNOI.js → Tile-DbNFNPfU.js} +189 -172
  15. package/dist/{TileImage-CmZf8EdU.js → TileImage-BsXBxMtq.js} +132 -102
  16. package/dist/{View-DcDc7N2K.js → View-BR92hTWP.js} +8 -8
  17. package/dist/{XmlPreview.client-C0OgBkSq.js → XmlPreview.client-DWJt61wG.js} +15 -15
  18. package/dist/{common-C4rDcQpp.js → common-PJfpC179.js} +34 -33
  19. package/dist/components-next.css +4 -4
  20. package/dist/components-next.js +130 -125
  21. package/dist/components.css +2 -2
  22. package/dist/{index-BRGqW8aQ.js → index-DVp7Y0Xu.js} +11105 -6743
  23. package/dist/{main-CNHxAJ8J.js → main-CrSRA2X-.js} +25208 -25053
  24. package/dist/{proj-CKwYjU38.js → proj-DsetBcW7.js} +513 -498
  25. package/dist/{tilecoord-YW3qEH_j.js → tilecoord-Db24Px13.js} +242 -224
  26. package/dist/{vue3-xml-viewer.common-CmAdQfIy.js → vue3-xml-viewer.common-BjA4LdSC.js} +1 -1
  27. package/package.json +3 -2
  28. package/src/components/DataserviceCard.vue +3 -3
  29. package/src/components/DatasetCard.vue +2 -2
  30. package/src/components/DatasetQuality.vue +23 -16
  31. package/src/components/DatasetQualityInline.vue +13 -17
  32. package/src/components/DatasetQualityScore.vue +12 -15
  33. package/src/components/DiscussionMessageCard.vue +1 -1
  34. package/src/components/ObjectCard.vue +2 -2
  35. package/src/components/ObjectCardHeader.vue +1 -1
  36. package/src/components/OrganizationHorizontalCard.vue +87 -0
  37. package/src/components/OrganizationNameWithCertificate.vue +1 -1
  38. package/src/components/ProgressBar.vue +31 -0
  39. package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
  40. package/src/components/ResourceAccordion/JsonPreview.client.vue +3 -3
  41. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -1
  42. package/src/components/ResourceAccordion/PdfPreview.client.vue +70 -74
  43. package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
  44. package/src/components/ResourceAccordion/Preview.vue +1 -1
  45. package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -8
  46. package/src/components/ResourceAccordion/XmlPreview.client.vue +3 -3
  47. package/src/components/ReuseHorizontalCard.vue +1 -1
  48. package/src/components/Search/Filter/ProducerTypeFilter.vue +13 -3
  49. package/src/components/Search/GlobalSearch.vue +124 -28
  50. package/src/components/Toggletip.vue +5 -2
  51. package/src/components/TopicCard.vue +1 -1
  52. package/src/composables/useHasTabularData.ts +15 -0
  53. package/src/composables/useResourceCapabilities.ts +3 -4
  54. package/src/composables/useTranslation.ts +2 -1
  55. package/src/functions/api.types.ts +1 -1
  56. package/src/main.ts +8 -1
  57. package/src/types/search.ts +29 -1
  58. package/dist/PdfPreview.client-CopqSDyt.js +0 -107
  59. package/dist/Pmtiles.client-mF6xaOO_.js +0 -22812
  60. package/dist/Swagger.client-eJ7gpfZA.js +0 -4
  61. package/dist/pdf-vue3-IkJO65RH.js +0 -273
  62. 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="primary"
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="shink-0 size-6" />
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="shink-0 size-6" />
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="shink-0 size-6" />
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 m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
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 options = [
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 || t('Ex : élection présidentielle 2022')"
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
- <label
182
- for="sort-search"
183
- class="fr-col-auto text-sm m-0 mr-2"
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
- <option :value="undefined">
194
- {{ t('Pertinence') }}
195
- </option>
196
- <option
197
- v-for="option in activeSortOptions"
198
- :key="option.value"
199
- :value="option.value"
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
- {{ option.label }}
202
- </option>
203
- </select>
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, RiRobot2Line, RiLineChartLine, RiLightbulbLine } from '@remixicon/vue'
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: RiRobot2Line,
577
- name: t('APIs'),
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="border-transparent -outline-offset-2 inline-flex items-center justify-center hover:bg-gray-some"
17
- :class="{ 'w-8 h-8 rounded-full bg-transparent': styledButton }"
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 m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
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 config.tabularApiUrl
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
- return primaryLang
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,
@@ -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 SearchTypeConfig = DatasetSearchConfig | DataserviceSearchConfig | ReuseSearchConfig
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
  }