@datagouv/components-next 1.0.2-dev.20 → 1.0.2-dev.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/assets/main.css +4 -0
- package/dist/{Control-DuZJdKV_.js → Control-B9WQxlpJ.js} +1 -1
- package/dist/{Datafair.client-Ds3VQrRi.js → Datafair.client-CFTmBFoY.js} +9 -9
- package/dist/{Event--kp8kMdJ.js → Event-iXDrp-fy.js} +31 -31
- package/dist/{Image-34hvypZI.js → Image-mNMBOofs.js} +9 -9
- package/dist/ImagePreview.client-BvDM3qjE.js +36 -0
- package/dist/JsonPreview.client-BP6gPRxf.js +40 -0
- package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
- package/dist/{MapContainer.client-B2BijMx5.js → MapContainer.client-D9N3664r.js} +41 -42
- package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
- package/dist/{PdfPreview.client-DJBMzrlC.js → PdfPreview.client-C6A6ZiKl.js} +835 -856
- package/dist/Pmtiles.client-B_sDZoqu.js +26333 -0
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-DGLXBys_.js +60 -0
- package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-VGQ7nkMS.js} +2 -2
- package/dist/{Tile-DbNFNPfU.js → Tile-CSpn0Ca8.js} +160 -163
- package/dist/{TileImage-BsXBxMtq.js → TileImage-CpbaEFwd.js} +143 -131
- package/dist/{View-BR92hTWP.js → View-BYBpt-AU.js} +483 -422
- package/dist/XmlPreview.client-DFQ1T-LN.js +34 -0
- package/dist/{common-PJfpC179.js → common-BoeTUnDZ.js} +52 -52
- package/dist/components-next.css +1 -52
- package/dist/components-next.js +196 -150
- package/dist/components.css +2 -2
- package/dist/{index-BFQX7Ccz.js → index-B_tPYi5Z.js} +34094 -27864
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-PeylHk0o.js} +162 -164
- package/dist/{main-vqhkVhVS.js → main-CVOZp_Nx.js} +41939 -73132
- package/dist/{proj-DsetBcW7.js → proj-DxGbobp7.js} +662 -626
- package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
- package/dist/{tilecoord-Db24Px13.js → tilecoord-DvX7WWiA.js} +273 -264
- package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
- package/dist/{vue3-xml-viewer.common-BcNJo35o.js → vue3-xml-viewer.common-CcF3Xd23.js} +1 -1
- package/package.json +47 -38
- package/src/chart.ts +5 -0
- package/src/components/ActivityList/ActivityList.vue +24 -14
- package/src/components/AppLink.vue +3 -0
- package/src/components/BannerAction.vue +6 -2
- package/src/components/Chart/ChartViewer.vue +320 -0
- package/src/components/Chart/ChartViewerWrapper.vue +182 -0
- package/src/components/CopyButton.vue +6 -2
- package/src/components/DataserviceCard.vue +24 -5
- package/src/components/DataserviceQuality.vue +82 -0
- package/src/components/DataserviceQualityInline.vue +31 -0
- package/src/components/DataserviceQualityTooltipContent.vue +79 -0
- package/src/components/DatasetCard.vue +24 -9
- package/src/components/DatasetInformation/DatasetTemporalitySection.vue +7 -4
- package/src/components/DatasetQuality.vue +11 -11
- package/src/components/DatasetQualityInline.vue +2 -2
- package/src/components/DatasetQualityTooltipContent.vue +1 -1
- package/src/components/DiscussionMessageCard.vue +14 -15
- package/src/components/Form/Listbox.vue +101 -0
- package/src/components/Form/SchemaSelect.vue +2 -0
- package/src/components/FormattedDate.vue +81 -0
- package/src/components/InfiniteLoader.vue +53 -0
- package/src/components/LoadingBlock.vue +4 -1
- package/src/components/ObjectCardHeader.vue +11 -4
- package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
- package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
- package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
- package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
- package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
- package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
- package/src/components/OpenApiViewer/openapi.ts +150 -0
- package/src/components/OrganizationNameWithCertificate.vue +3 -2
- package/src/components/Pagination.vue +25 -62
- package/src/components/PostCard.vue +13 -15
- package/src/components/RadioInput.vue +7 -2
- package/src/components/ResourceAccordion/DataStructure.vue +11 -33
- package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
- package/src/components/ResourceAccordion/Downloads.vue +172 -0
- package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
- package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
- package/src/components/ResourceAccordion/MapContainer.client.vue +2 -11
- package/src/components/ResourceAccordion/Metadata.vue +4 -6
- package/src/components/ResourceAccordion/PdfPreview.client.vue +58 -97
- package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
- package/src/components/ResourceAccordion/Pmtiles.client.vue +28 -13
- package/src/components/ResourceAccordion/Preview.vue +19 -15
- package/src/components/ResourceAccordion/PreviewWrapper.vue +89 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +36 -117
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
- package/src/components/ResourceExplorer/ResourceExplorer.vue +105 -210
- package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +79 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +104 -71
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +143 -291
- package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
- package/src/components/ResourceExplorer/ResourceSelector.vue +51 -0
- package/src/components/ResourceExplorer/ResourceViewerHeader.vue +122 -0
- package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +94 -0
- package/src/components/ResourceListItem.vue +168 -0
- package/src/components/ReuseCard.vue +28 -9
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/ReuseHorizontalCard.vue +2 -2
- package/src/components/SchemaCard.vue +10 -1
- package/src/components/Search/Filter/AccessTypeFilter.vue +2 -2
- package/src/components/Search/GlobalSearch.vue +229 -123
- package/src/components/Search/SearchInput.vue +5 -4
- package/src/components/Search/Sidemenu.vue +4 -3
- package/src/components/SmallChart.vue +11 -10
- package/src/components/StatBox.vue +13 -5
- package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
- package/src/components/TabularExplorer/TabularCell.vue +50 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +299 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +362 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
- package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
- package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
- package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
- package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
- package/src/components/TabularExplorer/TabularTable.vue +481 -0
- package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
- package/src/components/TabularExplorer/types.ts +84 -0
- package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
- package/src/components/TabularExplorer/useTabularContext.ts +82 -0
- package/src/components/TopicCard.vue +13 -14
- package/src/composables/useActiveDescendant.ts +5 -0
- package/src/composables/useDatasetResources.ts +164 -0
- package/src/composables/useHasTabularData.ts +15 -1
- package/src/composables/useMetrics.ts +1 -1
- package/src/composables/useResizable.ts +46 -0
- package/src/composables/useResourceCapabilities.ts +48 -40
- package/src/composables/useSearchFilter.ts +120 -0
- package/src/composables/useStableQueryParams.ts +38 -6
- package/src/composables/useTabularProfile.ts +70 -0
- package/src/composables/useTranslation.ts +24 -9
- package/src/config.ts +21 -3
- package/src/functions/activities.ts +3 -3
- package/src/functions/api.ts +20 -42
- package/src/functions/api.types.ts +1 -0
- package/src/functions/charts.ts +99 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/dates.ts +6 -7
- package/src/functions/helpers.ts +15 -0
- package/src/functions/metrics.ts +63 -60
- package/src/functions/paginate.ts +46 -0
- package/src/functions/resourceCapabilities.ts +34 -0
- package/src/functions/resources.ts +136 -1
- package/src/functions/schemas.ts +27 -0
- package/src/functions/scroll.ts +20 -0
- package/src/functions/tabular.ts +214 -0
- package/src/functions/tabularApi.ts +96 -11
- package/src/main.ts +119 -14
- package/src/types/dataservices.ts +17 -0
- package/src/types/organizations.ts +10 -0
- package/src/types/pages.ts +0 -5
- package/src/types/posts.ts +2 -2
- package/src/types/reports.ts +5 -1
- package/src/types/resources.ts +0 -1
- package/src/types/reuses.ts +12 -1
- package/src/types/search.ts +63 -1
- package/src/types/site.ts +5 -3
- package/src/types/ui.ts +2 -0
- package/src/types/users.ts +2 -1
- package/src/types/visualizations.ts +93 -0
- package/assets/swagger-themes/newspaper.css +0 -1670
- package/dist/JsonPreview.client-CYxmz0yV.js +0 -72
- package/dist/Pmtiles.client-C_hvRDuy.js +0 -25185
- package/dist/Swagger.client-B4y0hXME.js +0 -4
- package/dist/XmlPreview.client-LETEfNBH.js +0 -64
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
- /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
- /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
- /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="m-0 text-sm text-gray-medium">
|
|
3
|
+
<Toggletip
|
|
4
|
+
:styled-button="false"
|
|
5
|
+
button-class="border-transparent -outline-offset-2 inline-flex items-center justify-center hover:bg-gray-lower transition-colors"
|
|
6
|
+
:button-props="{ class: 'relative z-2 gap-1 rounded-sm px-1 -mx-1 group/quality', title: t('Qualité des métadonnées') }"
|
|
7
|
+
>
|
|
8
|
+
<RiInformationLine class="size-3.5 shrink-0" />
|
|
9
|
+
<span class="text-gray-medium text-sm group-hover/quality:underline">{{ t('Métadonnées :') }}</span>
|
|
10
|
+
<QualityScore :score="quality.score" />
|
|
11
|
+
<template #toggletip>
|
|
12
|
+
<DataserviceQualityTooltipContent :quality />
|
|
13
|
+
</template>
|
|
14
|
+
</Toggletip>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { RiInformationLine } from '@remixicon/vue'
|
|
20
|
+
import type { DataserviceQuality } from '../types/dataservices'
|
|
21
|
+
import QualityScore from './QualityScore.vue'
|
|
22
|
+
import DataserviceQualityTooltipContent from './DataserviceQualityTooltipContent.vue'
|
|
23
|
+
import Toggletip from './Toggletip.vue'
|
|
24
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
25
|
+
|
|
26
|
+
defineProps<{
|
|
27
|
+
quality: DataserviceQuality
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const { t } = useTranslation()
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<p class="text-sm text-gray-plain font-bold mb-0">
|
|
3
|
+
{{ t("Qualité des métadonnées :") }}
|
|
4
|
+
</p>
|
|
5
|
+
<ul class="list-none pl-0 space-y-2">
|
|
6
|
+
<QualityItem
|
|
7
|
+
:passed="quality.has_description"
|
|
8
|
+
:message-passed="t('Description renseignée')"
|
|
9
|
+
:message-failed="t('Description non renseignée')"
|
|
10
|
+
class="pb-0"
|
|
11
|
+
/>
|
|
12
|
+
<QualityItem
|
|
13
|
+
:passed="quality.has_machine_documentation"
|
|
14
|
+
:message-passed="t('Documentation machine (OpenAPI) disponible')"
|
|
15
|
+
:message-failed="t('Documentation machine (OpenAPI) manquante')"
|
|
16
|
+
class="pb-0"
|
|
17
|
+
/>
|
|
18
|
+
<QualityItem
|
|
19
|
+
:passed="quality.has_technical_documentation"
|
|
20
|
+
:message-passed="t('Documentation technique renseignée')"
|
|
21
|
+
:message-failed="t('Documentation technique manquante')"
|
|
22
|
+
class="pb-0"
|
|
23
|
+
/>
|
|
24
|
+
<QualityItem
|
|
25
|
+
:passed="quality.has_business_documentation"
|
|
26
|
+
:message-passed="t('Documentation métier renseignée')"
|
|
27
|
+
:message-failed="t('Documentation métier manquante')"
|
|
28
|
+
class="pb-0"
|
|
29
|
+
/>
|
|
30
|
+
<QualityItem
|
|
31
|
+
:passed="quality.license"
|
|
32
|
+
:message-passed="t('Licence renseignée')"
|
|
33
|
+
:message-failed="t('Licence non renseignée')"
|
|
34
|
+
class="pb-0"
|
|
35
|
+
/>
|
|
36
|
+
<QualityItem
|
|
37
|
+
:passed="quality.has_contact_point"
|
|
38
|
+
:message-passed="t('Point de contact renseigné')"
|
|
39
|
+
:message-failed="t('Point de contact non renseigné')"
|
|
40
|
+
class="pb-0"
|
|
41
|
+
/>
|
|
42
|
+
<QualityItem
|
|
43
|
+
:passed="quality.has_base_api_url"
|
|
44
|
+
:message-passed="t(`URL de base de l'API renseignée`)"
|
|
45
|
+
:message-failed="t(`URL de base de l'API non renseignée`)"
|
|
46
|
+
class="pb-0"
|
|
47
|
+
/>
|
|
48
|
+
<QualityItem
|
|
49
|
+
:passed="quality.availability_documented"
|
|
50
|
+
:message-passed="t('Disponibilité documentée')"
|
|
51
|
+
:message-failed="t('Disponibilité non documentée')"
|
|
52
|
+
class="pb-0"
|
|
53
|
+
/>
|
|
54
|
+
<QualityItem
|
|
55
|
+
:passed="quality.rate_limiting_documented"
|
|
56
|
+
:message-passed="t(`Limite d'appels documentée`)"
|
|
57
|
+
:message-failed="t(`Limite d'appels non documentée`)"
|
|
58
|
+
class="pb-0"
|
|
59
|
+
/>
|
|
60
|
+
<QualityItem
|
|
61
|
+
:passed="quality.access_conditions_clear"
|
|
62
|
+
:message-passed="t(`Conditions d'accès claires`)"
|
|
63
|
+
:message-failed="t(`Conditions d'accès non précisées`)"
|
|
64
|
+
class="pb-0"
|
|
65
|
+
/>
|
|
66
|
+
</ul>
|
|
67
|
+
</template>
|
|
68
|
+
|
|
69
|
+
<script setup lang="ts">
|
|
70
|
+
import type { DataserviceQuality } from '../types/dataservices'
|
|
71
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
72
|
+
import QualityItem from './QualityItem.vue'
|
|
73
|
+
|
|
74
|
+
defineProps<{
|
|
75
|
+
quality: DataserviceQuality
|
|
76
|
+
}>()
|
|
77
|
+
|
|
78
|
+
const { t } = useTranslation()
|
|
79
|
+
</script>
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
:icon="RiDatabase2Line"
|
|
37
37
|
:url="datasetUrl || dataset.page"
|
|
38
38
|
:target="datasetUrlInNewTab ? '_blank' : undefined"
|
|
39
|
+
:title-tag="titleTag"
|
|
39
40
|
>
|
|
40
41
|
{{ dataset.title }}
|
|
41
42
|
<template
|
|
@@ -55,9 +56,19 @@
|
|
|
55
56
|
:organization-url="organizationUrl"
|
|
56
57
|
/>
|
|
57
58
|
<RiSubtractLine class="hidden md:block size-4 flex-none fill-gray-medium" />
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
<TranslationT
|
|
60
|
+
tag="div"
|
|
61
|
+
class="w-full md:w-auto text-gray-medium whitespace-nowrap"
|
|
62
|
+
keypath="Mis à jour {date}"
|
|
63
|
+
>
|
|
64
|
+
<template #date>
|
|
65
|
+
<FormattedDate
|
|
66
|
+
:date="dataset.last_update"
|
|
67
|
+
format="relative"
|
|
68
|
+
:options="{ dateStyle: 'medium' }"
|
|
69
|
+
/>
|
|
70
|
+
</template>
|
|
71
|
+
</TranslationT>
|
|
61
72
|
</div>
|
|
62
73
|
<div class="mx-0 -mb-1 flex flex-wrap items-center text-sm text-gray-medium">
|
|
63
74
|
<div class="hidden sm:flex text-gray-medium">
|
|
@@ -106,10 +117,12 @@
|
|
|
106
117
|
</p>
|
|
107
118
|
</div>
|
|
108
119
|
</div>
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
<slot>
|
|
121
|
+
<ObjectCardShortDescription
|
|
122
|
+
v-if="showDescriptionShort"
|
|
123
|
+
:text="getDescriptionShort(props.dataset)"
|
|
124
|
+
/>
|
|
125
|
+
</slot>
|
|
113
126
|
</ObjectCard>
|
|
114
127
|
</template>
|
|
115
128
|
|
|
@@ -117,8 +130,8 @@
|
|
|
117
130
|
import type { RouteLocationRaw } from 'vue-router'
|
|
118
131
|
import { RiArchiveLine, RiDatabase2Line, RiDownloadLine, RiEyeLine, RiLineChartLine, RiLockLine, RiStarLine, RiSubtractLine } from '@remixicon/vue'
|
|
119
132
|
import type { Dataset, DatasetV2 } from '../types/datasets'
|
|
133
|
+
import type { TitleTag } from '../types/ui'
|
|
120
134
|
import { summarize } from '../functions/helpers'
|
|
121
|
-
import { useFormatDate } from '../functions/dates'
|
|
122
135
|
import { getDescriptionShort } from '../functions/description'
|
|
123
136
|
import { useTranslation } from '../composables/useTranslation'
|
|
124
137
|
import DatasetQualityInline from './DatasetQualityInline.vue'
|
|
@@ -130,6 +143,8 @@ import ObjectCard from './ObjectCard.vue'
|
|
|
130
143
|
import ObjectCardBadge from './ObjectCardBadge.vue'
|
|
131
144
|
import ObjectCardHeader from './ObjectCardHeader.vue'
|
|
132
145
|
import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
|
|
146
|
+
import FormattedDate from './FormattedDate.vue'
|
|
147
|
+
import TranslationT from './TranslationT.vue'
|
|
133
148
|
|
|
134
149
|
type Props = {
|
|
135
150
|
dataset: Dataset | DatasetV2
|
|
@@ -137,6 +152,7 @@ type Props = {
|
|
|
137
152
|
datasetUrlInNewTab?: boolean
|
|
138
153
|
organizationUrl?: RouteLocationRaw
|
|
139
154
|
showDescriptionShort?: boolean
|
|
155
|
+
titleTag?: TitleTag
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -144,5 +160,4 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
144
160
|
})
|
|
145
161
|
|
|
146
162
|
const { t } = useTranslation()
|
|
147
|
-
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
148
163
|
</script>
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
<DescriptionList>
|
|
7
7
|
<div>
|
|
8
8
|
<DescriptionListTerm>{{ t('Création') }}</DescriptionListTerm>
|
|
9
|
-
<DescriptionListDetails>
|
|
9
|
+
<DescriptionListDetails>
|
|
10
|
+
<FormattedDate :date="dataset.created_at" />
|
|
11
|
+
</DescriptionListDetails>
|
|
10
12
|
</div>
|
|
11
13
|
<div v-if="dataset.frequency">
|
|
12
14
|
<DescriptionListTerm>{{ t('Fréquence') }}</DescriptionListTerm>
|
|
@@ -20,7 +22,9 @@
|
|
|
20
22
|
</div>
|
|
21
23
|
<div>
|
|
22
24
|
<DescriptionListTerm>{{ t('Dernière mise à jour') }}</DescriptionListTerm>
|
|
23
|
-
<DescriptionListDetails>
|
|
25
|
+
<DescriptionListDetails>
|
|
26
|
+
<FormattedDate :date="dataset.last_update" />
|
|
27
|
+
</DescriptionListDetails>
|
|
24
28
|
</div>
|
|
25
29
|
<slot />
|
|
26
30
|
</DescriptionList>
|
|
@@ -29,17 +33,16 @@
|
|
|
29
33
|
|
|
30
34
|
<script setup lang="ts">
|
|
31
35
|
import { useTranslation } from '../../composables/useTranslation'
|
|
32
|
-
import { useFormatDate } from '../../functions/dates'
|
|
33
36
|
import type { DatasetV2WithFullObject } from '../../types/datasets'
|
|
34
37
|
import DescriptionList from '../DescriptionList.vue'
|
|
35
38
|
import DescriptionListTerm from '../DescriptionListTerm.vue'
|
|
36
39
|
import DescriptionListDetails from '../DescriptionListDetails.vue'
|
|
37
40
|
import DateRangeDetails from '../DateRangeDetails.vue'
|
|
41
|
+
import FormattedDate from '../FormattedDate.vue'
|
|
38
42
|
|
|
39
43
|
defineProps<{
|
|
40
44
|
dataset: DatasetV2WithFullObject
|
|
41
45
|
}>()
|
|
42
46
|
|
|
43
47
|
const { t } = useTranslation()
|
|
44
|
-
const { formatDate } = useFormatDate()
|
|
45
48
|
</script>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{{ t('Qualité des métadonnées:') }}
|
|
14
14
|
</span>
|
|
15
15
|
</span>
|
|
16
|
-
<
|
|
16
|
+
<QualityScore
|
|
17
17
|
:score="quality.score"
|
|
18
18
|
class="w-full"
|
|
19
19
|
/>
|
|
@@ -23,35 +23,35 @@
|
|
|
23
23
|
</Toggletip>
|
|
24
24
|
<template v-if="!hideWarnings">
|
|
25
25
|
<ul class="list-none pl-0">
|
|
26
|
-
<
|
|
26
|
+
<QualityItemWarning
|
|
27
27
|
:quality-item="quality.dataset_description_quality"
|
|
28
28
|
:message="t('Description des données non renseignée')"
|
|
29
29
|
/>
|
|
30
|
-
<
|
|
30
|
+
<QualityItemWarning
|
|
31
31
|
:quality-item="quality.resources_documentation"
|
|
32
32
|
:message="t('Documentation des fichiers manquante')"
|
|
33
33
|
/>
|
|
34
|
-
<
|
|
34
|
+
<QualityItemWarning
|
|
35
35
|
:quality-item="quality.license"
|
|
36
36
|
:message="t('Licence non renseignée')"
|
|
37
37
|
/>
|
|
38
|
-
<
|
|
38
|
+
<QualityItemWarning
|
|
39
39
|
:quality-item="quality.update_frequency && quality.update_fulfilled_in_time"
|
|
40
40
|
:message="quality.update_frequency ? t('Fréquence de mise à jour non respectée') : t('Fréquence de mise à jour non renseignée')"
|
|
41
41
|
/>
|
|
42
|
-
<
|
|
42
|
+
<QualityItemWarning
|
|
43
43
|
:quality-item="quality.has_open_format"
|
|
44
44
|
:message="t('Formats de fichiers non standards')"
|
|
45
45
|
/>
|
|
46
|
-
<
|
|
46
|
+
<QualityItemWarning
|
|
47
47
|
:quality-item="quality.temporal_coverage"
|
|
48
48
|
:message="t('Couverture temporelle non renseignée')"
|
|
49
49
|
/>
|
|
50
|
-
<
|
|
50
|
+
<QualityItemWarning
|
|
51
51
|
:quality-item="quality.spatial"
|
|
52
52
|
:message="t('Couverture spatiale non renseignée')"
|
|
53
53
|
/>
|
|
54
|
-
<
|
|
54
|
+
<QualityItemWarning
|
|
55
55
|
:quality-item="quality.all_resources_available"
|
|
56
56
|
:message="t('Certains fichiers ne sont pas disponibles')"
|
|
57
57
|
/>
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
<script setup lang="ts">
|
|
63
63
|
import { RiInformationLine } from '@remixicon/vue'
|
|
64
64
|
import type { Quality } from '../types/datasets'
|
|
65
|
-
import
|
|
66
|
-
import
|
|
65
|
+
import QualityItemWarning from './QualityItemWarning.vue'
|
|
66
|
+
import QualityScore from './QualityScore.vue'
|
|
67
67
|
import Toggletip from './Toggletip.vue'
|
|
68
68
|
import DatasetQualityTooltipContent from './DatasetQualityTooltipContent.vue'
|
|
69
69
|
import { useTranslation } from '../composables/useTranslation'
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
>
|
|
8
8
|
<RiInformationLine class="size-3.5 shrink-0" />
|
|
9
9
|
<span class="text-gray-medium text-sm group-hover/quality:underline">{{ t('Métadonnées :') }}</span>
|
|
10
|
-
<
|
|
10
|
+
<QualityScore :score="quality.score" />
|
|
11
11
|
<template #toggletip>
|
|
12
12
|
<DatasetQualityTooltipContent :quality />
|
|
13
13
|
</template>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
19
|
import { RiInformationLine } from '@remixicon/vue'
|
|
20
20
|
import type { Quality } from '../types/datasets'
|
|
21
|
-
import
|
|
21
|
+
import QualityScore from './QualityScore.vue'
|
|
22
22
|
import DatasetQualityTooltipContent from './DatasetQualityTooltipContent.vue'
|
|
23
23
|
import Toggletip from './Toggletip.vue'
|
|
24
24
|
import { useTranslation } from '../composables/useTranslation'
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
import type { Quality } from '../types/datasets'
|
|
69
69
|
import { useComponentsConfig } from '../config'
|
|
70
70
|
import { useTranslation } from '../composables/useTranslation'
|
|
71
|
-
import DatasetQualityItem from './
|
|
71
|
+
import DatasetQualityItem from './QualityItem.vue'
|
|
72
72
|
|
|
73
73
|
defineProps<{
|
|
74
74
|
quality: Quality
|
|
@@ -20,9 +20,18 @@
|
|
|
20
20
|
aria-hidden="true"
|
|
21
21
|
class="size-4 flex-none fill-gray-medium"
|
|
22
22
|
/>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<TranslationT
|
|
24
|
+
class="text-gray-medium whitespace-nowrap"
|
|
25
|
+
keypath="Posté {date}"
|
|
26
|
+
>
|
|
27
|
+
<template #date>
|
|
28
|
+
<FormattedDate
|
|
29
|
+
:date="discussion.created"
|
|
30
|
+
format="relative"
|
|
31
|
+
:options="{ dateStyle: 'long', timeStyle: 'short' }"
|
|
32
|
+
/>
|
|
33
|
+
</template>
|
|
34
|
+
</TranslationT>
|
|
26
35
|
</div>
|
|
27
36
|
|
|
28
37
|
<ObjectCardShortDescription :text="firstMessageContent" />
|
|
@@ -35,13 +44,13 @@
|
|
|
35
44
|
import { RiChat3Line, RiSubtractLine } from '@remixicon/vue'
|
|
36
45
|
import { computed } from 'vue'
|
|
37
46
|
import type { RouteLocationRaw } from 'vue-router'
|
|
38
|
-
import { useFormatDate } from '../functions/dates'
|
|
39
|
-
import { useTranslation } from '../composables/useTranslation'
|
|
40
47
|
import type { Thread } from '../types/discussions'
|
|
41
48
|
import ObjectCard from './ObjectCard.vue'
|
|
42
49
|
import ObjectCardHeader from './ObjectCardHeader.vue'
|
|
43
50
|
import ObjectCardOwner from './ObjectCardOwner.vue'
|
|
44
51
|
import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
|
|
52
|
+
import FormattedDate from './FormattedDate.vue'
|
|
53
|
+
import TranslationT from './TranslationT.vue'
|
|
45
54
|
|
|
46
55
|
const props = defineProps<{
|
|
47
56
|
discussion: Thread
|
|
@@ -49,15 +58,5 @@ const props = defineProps<{
|
|
|
49
58
|
organizationUrl?: RouteLocationRaw
|
|
50
59
|
}>()
|
|
51
60
|
|
|
52
|
-
const { t } = useTranslation()
|
|
53
|
-
const { formatRelativeIfRecentDate } = useFormatDate()
|
|
54
|
-
|
|
55
61
|
const firstMessageContent = computed(() => props.discussion.discussion?.[0]?.content)
|
|
56
|
-
|
|
57
|
-
const formatDate = (dateString: string) => {
|
|
58
|
-
return formatRelativeIfRecentDate(dateString, {
|
|
59
|
-
dateStyle: 'long',
|
|
60
|
-
timeStyle: 'short',
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
62
|
</script>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Listbox v-model="model">
|
|
3
|
+
<div class="relative min-w-0">
|
|
4
|
+
<div
|
|
5
|
+
ref="floatingReference"
|
|
6
|
+
class="relative w-full cursor-default overflow-hidden bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm"
|
|
7
|
+
>
|
|
8
|
+
<ListboxButton class="input shadow-input text-sm flex items-center gap-2">
|
|
9
|
+
<slot name="button">
|
|
10
|
+
<div class="w-full flex items-center justify-between gap-2">
|
|
11
|
+
<div
|
|
12
|
+
class="truncate"
|
|
13
|
+
:class="{ 'text-new-disabled-text': isDisabled(model) }"
|
|
14
|
+
>
|
|
15
|
+
{{ displayValue(model) }}
|
|
16
|
+
</div>
|
|
17
|
+
<RiArrowDownSLine class="flex-none size-4 justify-self-end" />
|
|
18
|
+
</div>
|
|
19
|
+
</slot>
|
|
20
|
+
</ListboxButton>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<ListboxOptions
|
|
24
|
+
ref="popover"
|
|
25
|
+
:style="floatingStyles"
|
|
26
|
+
class="z-10 mt-1 absolute max-h-60 min-w-80 w-full overflow-auto rounded-md bg-white text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm pl-0"
|
|
27
|
+
>
|
|
28
|
+
<ListboxOption
|
|
29
|
+
v-for="option in options"
|
|
30
|
+
:key="getOptionId(toValue(option))"
|
|
31
|
+
v-slot="{ active, selected }"
|
|
32
|
+
as="template"
|
|
33
|
+
:value="option"
|
|
34
|
+
>
|
|
35
|
+
<li
|
|
36
|
+
class="relative cursor-default select-none py-2 pr-4 list-none flex items-center gap-2 text-gray-900"
|
|
37
|
+
:class="{
|
|
38
|
+
'bg-gray-lower': active && !isDisabled(toValue(option)),
|
|
39
|
+
'text-new-disabled-text': isDisabled(toValue(option)),
|
|
40
|
+
'pl-2': selected,
|
|
41
|
+
'pl-6': !selected,
|
|
42
|
+
}"
|
|
43
|
+
>
|
|
44
|
+
<div class="flex items-center justify-center aspect-square">
|
|
45
|
+
<RiCheckLine
|
|
46
|
+
v-if="selected"
|
|
47
|
+
class="size-4"
|
|
48
|
+
:class="isDisabled(toValue(option)) ?' text-new-disabled-text' : 'text-new-primary'"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<slot
|
|
52
|
+
name="option"
|
|
53
|
+
v-bind="{ option, active }"
|
|
54
|
+
>
|
|
55
|
+
{{ displayValue(option) }}
|
|
56
|
+
</slot>
|
|
57
|
+
</li>
|
|
58
|
+
</ListboxOption>
|
|
59
|
+
</ListboxOptions>
|
|
60
|
+
</div>
|
|
61
|
+
</Listbox>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup lang="ts" generic="T extends string | number | object">
|
|
65
|
+
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/vue'
|
|
66
|
+
import { useFloating, autoUpdate, autoPlacement } from '@floating-ui/vue'
|
|
67
|
+
import { toValue, useTemplateRef } from 'vue'
|
|
68
|
+
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/vue'
|
|
69
|
+
|
|
70
|
+
withDefaults(defineProps<{
|
|
71
|
+
options?: Array<T>
|
|
72
|
+
getOptionId?: (option: T) => string | number
|
|
73
|
+
displayValue: (option: T | null) => string
|
|
74
|
+
isDisabled?: (option: T | null) => boolean
|
|
75
|
+
}>(), {
|
|
76
|
+
getOptionId: (option: T): string | number => {
|
|
77
|
+
if (typeof option === 'string') return option
|
|
78
|
+
if (typeof option === 'number') return option
|
|
79
|
+
if (typeof option === 'object' && 'id' in option) return option.id as string
|
|
80
|
+
|
|
81
|
+
throw new Error('Please set getOptionId()')
|
|
82
|
+
},
|
|
83
|
+
isDisabled: (option: T | null): boolean => {
|
|
84
|
+
if (option && typeof option === 'object' && 'disabled' in option) return option.disabled as boolean
|
|
85
|
+
|
|
86
|
+
return false
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const model = defineModel<T | null>({ required: true })
|
|
91
|
+
|
|
92
|
+
const referenceRef = useTemplateRef('floatingReference')
|
|
93
|
+
const floatingRef = useTemplateRef<InstanceType<typeof ListboxOptions>>('popover')
|
|
94
|
+
const { floatingStyles } = useFloating(referenceRef, floatingRef, {
|
|
95
|
+
middleware: [autoPlacement({
|
|
96
|
+
allowedPlacements: ['bottom-start', 'bottom', 'bottom-end'],
|
|
97
|
+
crossAxis: true,
|
|
98
|
+
})],
|
|
99
|
+
whileElementsMounted: autoUpdate,
|
|
100
|
+
})
|
|
101
|
+
</script>
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
:placeholder="t('Tous les schémas')"
|
|
9
9
|
:get-option-id="(s: RegisteredSchema) => s.name"
|
|
10
10
|
:display-value="(s: RegisteredSchema) => s.name"
|
|
11
|
+
:filter="schemaMatchesQuery"
|
|
11
12
|
:multiple="false"
|
|
12
13
|
/>
|
|
13
14
|
</template>
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
import { useTranslation } from '../../composables/useTranslation'
|
|
17
18
|
import { useSelectModelSync } from '../../composables/useSelectModelSync'
|
|
18
19
|
import { useFetch } from '../../functions/api'
|
|
20
|
+
import { schemaMatchesQuery } from '../../functions/schemas'
|
|
19
21
|
import type { RegisteredSchema } from '../../types/schemas'
|
|
20
22
|
import SearchableSelect from './SearchableSelect.vue'
|
|
21
23
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- An instant is formatted in the server's timezone at render time and in the
|
|
3
|
+
reader's at read time, so its text and `title` legitimately differ and Vue is
|
|
4
|
+
told to accept it. A plain date carries no time, so nothing about it varies —
|
|
5
|
+
exempting it would hide a real bug instead of a legitimate difference. Vue only
|
|
6
|
+
honours `data-allow-mismatch` on an element whose children are plain text,
|
|
7
|
+
never on a text node coming from a slot: hence two static elements instead of a
|
|
8
|
+
`<component :is>`, which would pass the text through a slot. -->
|
|
9
|
+
<time
|
|
10
|
+
v-if="datetime"
|
|
11
|
+
:datetime
|
|
12
|
+
:title
|
|
13
|
+
:data-allow-mismatch="allowMismatch"
|
|
14
|
+
>{{ formatted }}</time>
|
|
15
|
+
<!-- No date to format: nothing here depends on the timezone or on "now". -->
|
|
16
|
+
<span v-else>{{ formatted }}</span>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import { computed } from 'vue'
|
|
21
|
+
import { useFormatDate } from '../functions/dates'
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<{
|
|
24
|
+
date: Date | string | null | undefined
|
|
25
|
+
/**
|
|
26
|
+
* `date`: the full date. `relative`: "3 days ago" while the date is less than a
|
|
27
|
+
* month old, the full date beyond. `from-now`: always relative, however old.
|
|
28
|
+
*/
|
|
29
|
+
format?: 'date' | 'relative' | 'from-now'
|
|
30
|
+
options?: Intl.DateTimeFormatOptions
|
|
31
|
+
}>(), {
|
|
32
|
+
format: 'date',
|
|
33
|
+
options: () => ({}),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const { formatDate, formatFromNow, formatRelativeIfRecentDate } = useFormatDate()
|
|
37
|
+
|
|
38
|
+
// `2026`, `2026-04` and `2026-04-24` carry no time of day. `new Date()` reads them as
|
|
39
|
+
// midnight UTC, an instant they never claimed, and then converts it: a reader west of
|
|
40
|
+
// UTC gets the day before — and with it the month, and sometimes the year.
|
|
41
|
+
const PLAIN_DATE = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/
|
|
42
|
+
|
|
43
|
+
const plainDate = computed(() => typeof props.date === 'string' ? props.date.match(PLAIN_DATE) : null)
|
|
44
|
+
|
|
45
|
+
const parsed = computed(() => {
|
|
46
|
+
if (!props.date) return null
|
|
47
|
+
const plain = plainDate.value
|
|
48
|
+
const date = plain
|
|
49
|
+
? new Date(Number(plain[1]), Number(plain[2] ?? 1) - 1, Number(plain[3] ?? 1))
|
|
50
|
+
: new Date(props.date)
|
|
51
|
+
return isNaN(date.getTime()) ? null : date
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Announce the precision the value carries, never more: a month stays a month.
|
|
55
|
+
const datetime = computed(() => {
|
|
56
|
+
if (!parsed.value) return undefined
|
|
57
|
+
return plainDate.value ? plainDate.value[0] : parsed.value.toISOString()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// Only what genuinely differs between server and browser is exempted: an instant, and
|
|
61
|
+
// the relative forms, which are measured against "now".
|
|
62
|
+
const allowMismatch = computed(() => !plainDate.value || props.format !== 'date'
|
|
63
|
+
? 'text,attribute'
|
|
64
|
+
: undefined)
|
|
65
|
+
|
|
66
|
+
const formatted = computed(() => {
|
|
67
|
+
if (props.format === 'relative') return formatRelativeIfRecentDate(parsed.value, props.options)
|
|
68
|
+
if (props.format === 'from-now') return formatFromNow(parsed.value)
|
|
69
|
+
return formatDate(parsed.value, props.options)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Hovering an instant gives the exact moment, which the displayed form usually omits:
|
|
73
|
+
// the relative forms drop it entirely, and most styles drop the time. A plain date has
|
|
74
|
+
// no moment to reveal — a `title` there would be one we made up, and a form that already
|
|
75
|
+
// spells the moment out needs no tooltip repeating it word for word.
|
|
76
|
+
const title = computed(() => {
|
|
77
|
+
if (!parsed.value || plainDate.value) return undefined
|
|
78
|
+
const exact = formatDate(parsed.value, { dateStyle: 'long', timeStyle: 'short' })
|
|
79
|
+
return exact === formatted.value ? undefined : exact
|
|
80
|
+
})
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="sentinel">
|
|
3
|
+
<slot>
|
|
4
|
+
<div class="flex items-center justify-center p-4">
|
|
5
|
+
<span class="inline-flex items-center gap-2 text-xs text-gray-medium">
|
|
6
|
+
<RiLoader4Line
|
|
7
|
+
class="size-4 animate-spin"
|
|
8
|
+
aria-hidden="true"
|
|
9
|
+
/>
|
|
10
|
+
{{ t('Chargement…') }}
|
|
11
|
+
</span>
|
|
12
|
+
</div>
|
|
13
|
+
</slot>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { onMounted, onUnmounted, useTemplateRef, watch } from 'vue'
|
|
19
|
+
import { RiLoader4Line } from '@remixicon/vue'
|
|
20
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
21
|
+
|
|
22
|
+
const props = defineProps<{
|
|
23
|
+
root?: HTMLElement | null
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits<{
|
|
27
|
+
intersect: []
|
|
28
|
+
}>()
|
|
29
|
+
|
|
30
|
+
const { t } = useTranslation()
|
|
31
|
+
|
|
32
|
+
const sentinelRef = useTemplateRef<HTMLElement>('sentinel')
|
|
33
|
+
let observer: IntersectionObserver | null = null
|
|
34
|
+
|
|
35
|
+
function setupObserver() {
|
|
36
|
+
observer?.disconnect()
|
|
37
|
+
const el = sentinelRef.value
|
|
38
|
+
if (!el) return
|
|
39
|
+
observer = new IntersectionObserver(
|
|
40
|
+
(entries) => {
|
|
41
|
+
if (entries[0]?.isIntersecting) {
|
|
42
|
+
emit('intersect')
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{ root: props.root ?? null, rootMargin: '200px' },
|
|
46
|
+
)
|
|
47
|
+
observer.observe(el)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onMounted(setupObserver)
|
|
51
|
+
watch([sentinelRef, () => props.root], setupObserver)
|
|
52
|
+
onUnmounted(() => observer?.disconnect())
|
|
53
|
+
</script>
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative">
|
|
3
3
|
<div :class="{ 'opacity-50 min-h-64': loading }">
|
|
4
|
+
<!-- Keep the previous content while reloading, dimmed under the loader:
|
|
5
|
+
dropping it collapses the page height, which makes the browser clamp
|
|
6
|
+
the scroll and throws the reader back to the top. -->
|
|
4
7
|
<slot
|
|
5
|
-
v-if="!
|
|
8
|
+
v-if="!hasError && data"
|
|
6
9
|
:data="data"
|
|
7
10
|
/>
|
|
8
11
|
<slot
|