@datagouv/components-next 1.0.2-dev.162 → 1.0.2-dev.164
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/dist/{Datafair.client-DXPU1V24.js → Datafair.client-B-OG5WtK.js} +1 -1
- package/dist/ImagePreview.client-D0c-ifUi.js +36 -0
- package/dist/{JsonPreview.client-D1SnGfWG.js → JsonPreview.client-Dwh9sW9M.js} +2 -2
- package/dist/{MapContainer.client-DlsaEVGx.js → MapContainer.client-C2AZ7sOB.js} +2 -2
- package/dist/{PdfPreview.client-C9OrLR0z.js → PdfPreview.client-8NzNfW8b.js} +2 -2
- package/dist/{Pmtiles.client-CyylWfTy.js → Pmtiles.client-BFH4rTB7.js} +5842 -5660
- package/dist/{PreviewWrapper.vue_vue_type_script_setup_true_lang-CtXZOHXI.js → PreviewWrapper.vue_vue_type_script_setup_true_lang-BOgE441d.js} +7 -10
- package/dist/{XmlPreview.client-DoX7RurH.js → XmlPreview.client-DMOUO508.js} +3 -3
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +179 -171
- package/dist/components.css +1 -1
- package/dist/{index-Br1sa7wQ.js → index-C9obT9DJ.js} +4392 -4293
- package/dist/{leaflet-src-DWG7tmpQ.js → leaflet-src-B4AZU0vr.js} +1 -1
- package/dist/{main-BU6kP4dE.js → main-DNoYWfit.js} +16159 -15631
- package/dist/{vue3-xml-viewer.common-Cpui-Yi9.js → vue3-xml-viewer.common-Cp7Cu5ME.js} +1 -1
- package/package.json +4 -5
- package/src/components/Chart/ChartViewer.vue +103 -10
- package/src/components/Chart/ChartViewerWrapper.vue +8 -0
- package/src/components/DataserviceCard.vue +6 -0
- package/src/components/DataserviceQuality.vue +82 -0
- package/src/components/DataserviceQualityInline.vue +31 -0
- package/src/components/DataserviceQualityTooltipContent.vue +79 -0
- package/src/components/DatasetQuality.vue +11 -11
- package/src/components/DatasetQualityInline.vue +2 -2
- package/src/components/DatasetQualityTooltipContent.vue +1 -1
- package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +8 -3
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +15 -1
- package/src/components/ReuseCard.vue +2 -2
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/ReuseHorizontalCard.vue +2 -2
- package/src/components/Search/GlobalSearch.vue +2 -2
- package/src/components/TabularExplorer/TabularCell.vue +5 -4
- package/src/components/TabularExplorer/TabularCellPopover.vue +1 -1
- package/src/components/TabularExplorer/TabularExplorer.vue +168 -49
- package/src/components/TabularExplorer/TabularFilterContent.vue +27 -10
- package/src/components/TabularExplorer/TabularFilterPopover.vue +1 -0
- package/src/components/TabularExplorer/types.ts +3 -3
- package/src/composables/useHasTabularData.ts +2 -1
- package/src/composables/useResourceCapabilities.ts +27 -14
- package/src/config.ts +1 -0
- package/src/functions/charts.ts +23 -3
- package/src/functions/resources.ts +56 -0
- package/src/functions/tabular.ts +44 -1
- package/src/main.ts +15 -6
- package/src/types/dataservices.ts +15 -0
- package/src/types/organizations.ts +10 -0
- package/src/types/reuses.ts +12 -1
- package/src/types/search.ts +2 -2
- package/src/types/visualizations.ts +1 -1
- /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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagouv/components-next",
|
|
3
|
-
"version": "1.0.2-dev.
|
|
3
|
+
"version": "1.0.2-dev.164",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"src"
|
|
24
24
|
],
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@floating-ui/vue": "^
|
|
26
|
+
"@floating-ui/vue": "^2.0.1",
|
|
27
27
|
"@headlessui/vue": "^1.7.23",
|
|
28
28
|
"@remixicon/vue": "^4.9.0",
|
|
29
29
|
"@types/hast": "^3.0.4",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@vueuse/core": "^14.3.0",
|
|
32
32
|
"@vueuse/router": "^14.3.0",
|
|
33
33
|
"chart.js": "^4.5.1",
|
|
34
|
-
"dompurify": "^3.4.
|
|
34
|
+
"dompurify": "^3.4.11",
|
|
35
35
|
"echarts": "^6.1.0",
|
|
36
36
|
"geopf-extensions-openlayers": "1.0.0-beta.11",
|
|
37
37
|
"leaflet": "^1.9.4",
|
|
@@ -63,7 +63,6 @@
|
|
|
63
63
|
"yaml": "^2.9.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@intlify/cli": "^0.13.1",
|
|
67
66
|
"@tailwindcss/cli": "^4.3.0",
|
|
68
67
|
"@tailwindcss/typography": "^0.5.19",
|
|
69
68
|
"@tsconfig/node22": "^22.0.5",
|
|
@@ -83,7 +82,7 @@
|
|
|
83
82
|
"typescript": "^5.9.3",
|
|
84
83
|
"vite": "^7.3.3",
|
|
85
84
|
"vite-plugin-vue-devtools": "^8.1.2",
|
|
86
|
-
"vue": "^3.5.
|
|
85
|
+
"vue": "^3.5.40",
|
|
87
86
|
"vue-router": "^5.0.7",
|
|
88
87
|
"vue-tsc": "^3.3.2"
|
|
89
88
|
},
|
|
@@ -13,7 +13,8 @@ import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, Datas
|
|
|
13
13
|
import { init, type ECharts as EChartsType } from 'echarts'
|
|
14
14
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|
15
15
|
import { summarize } from '../../functions/helpers'
|
|
16
|
-
import
|
|
16
|
+
import { useFormatDate } from '../../functions/dates'
|
|
17
|
+
import type { Chart, ColumnDefinition, ColumnsDefinition, XAxis, YAxis, XAxisForm, ChartForApi } from '../../types/visualizations'
|
|
17
18
|
import { useTranslation } from '../../composables/useTranslation'
|
|
18
19
|
|
|
19
20
|
use([CanvasRenderer, LineChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, DatasetComponent])
|
|
@@ -22,17 +23,26 @@ const props = defineProps<{
|
|
|
22
23
|
chart: Chart | ChartForApi
|
|
23
24
|
series: {
|
|
24
25
|
data: Record<string, Array<Record<string, unknown>>>
|
|
25
|
-
columns:
|
|
26
|
+
columns: ColumnsDefinition
|
|
26
27
|
}
|
|
27
28
|
}>()
|
|
28
29
|
|
|
29
30
|
const { locale } = useTranslation()
|
|
31
|
+
const { formatDate } = useFormatDate()
|
|
30
32
|
const chartContainer = ref<HTMLElement | null>(null)
|
|
31
33
|
let echartsInstance: EChartsType | null = null
|
|
32
34
|
|
|
33
|
-
function
|
|
35
|
+
function capture(): string | null {
|
|
36
|
+
return echartsInstance?.getDataURL() ?? null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
defineExpose({ capture })
|
|
40
|
+
|
|
41
|
+
// ECharts has a dedicated 'time' axis type for continuous date values.
|
|
42
|
+
function mapXAxisType(xAxis: XAxis | XAxisForm): 'category' | 'value' | 'time' {
|
|
34
43
|
if (!xAxis) return 'category'
|
|
35
|
-
|
|
44
|
+
if (xAxis.type !== 'continuous') return 'category'
|
|
45
|
+
return isDateAxis.value ? 'time' : 'value'
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefined {
|
|
@@ -44,6 +54,77 @@ function buildYAxisFormatter(yAxis: YAxis): ((value: number) => string) | undefi
|
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
function buildXAxisFormatter(column: ColumnDefinition | null, options?: Intl.DateTimeFormatOptions): ((value: string | number | Date) => string) | undefined {
|
|
58
|
+
if (column?.type !== 'date') return undefined
|
|
59
|
+
return (value: string | number | Date) => {
|
|
60
|
+
if (value === null || value === undefined) return ''
|
|
61
|
+
const date = value instanceof Date ? value : new Date(value)
|
|
62
|
+
if (Number.isNaN(date.getTime())) return String(value)
|
|
63
|
+
return formatDate(date, options ?? { dateStyle: 'short' })
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseXValueAsDate(row: Record<string, unknown>, xColumn: string): Record<string, unknown> {
|
|
68
|
+
const value = row[xColumn]
|
|
69
|
+
if (value === null || value === undefined || value instanceof Date) return row
|
|
70
|
+
if (typeof value === 'string') {
|
|
71
|
+
const date = new Date(value)
|
|
72
|
+
if (!Number.isNaN(date.getTime())) {
|
|
73
|
+
return { ...row, [xColumn]: date }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return row
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const xAxisColumn = computed(() => {
|
|
80
|
+
const columnName = props.chart.x_axis.column_x
|
|
81
|
+
if (!columnName) return null
|
|
82
|
+
for (const columns of Object.values(props.series.columns)) {
|
|
83
|
+
const column = columns.find(c => c.name === columnName)
|
|
84
|
+
if (column) return column
|
|
85
|
+
}
|
|
86
|
+
return null
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
const isDateAxis = computed(() => xAxisColumn.value?.type === 'date')
|
|
90
|
+
|
|
91
|
+
const xAxisBounds = computed(() => {
|
|
92
|
+
const columnName = props.chart.x_axis.column_x
|
|
93
|
+
if (!columnName) return { min: undefined, max: undefined }
|
|
94
|
+
|
|
95
|
+
let min: number | undefined
|
|
96
|
+
let max: number | undefined
|
|
97
|
+
|
|
98
|
+
for (const s of props.chart.series) {
|
|
99
|
+
const xColumn = s.column_x_name_override ?? columnName
|
|
100
|
+
const data = props.series.data[s.resource_id]
|
|
101
|
+
if (!data) continue
|
|
102
|
+
|
|
103
|
+
for (const row of data) {
|
|
104
|
+
const value = row[xColumn]
|
|
105
|
+
if (value === null || value === undefined) continue
|
|
106
|
+
|
|
107
|
+
let numericValue: number | undefined
|
|
108
|
+
if (value instanceof Date) {
|
|
109
|
+
numericValue = value.getTime()
|
|
110
|
+
}
|
|
111
|
+
else if (isDateAxis.value && typeof value === 'string') {
|
|
112
|
+
numericValue = new Date(value).getTime()
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
numericValue = Number(value)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (numericValue === undefined || Number.isNaN(numericValue)) continue
|
|
119
|
+
|
|
120
|
+
if (min === undefined || numericValue < min) min = numericValue
|
|
121
|
+
if (max === undefined || numericValue > max) max = numericValue
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { min, max }
|
|
126
|
+
})
|
|
127
|
+
|
|
47
128
|
const echartsOption = computed(() => {
|
|
48
129
|
const seriesCount = props.chart.series.length
|
|
49
130
|
if (!props.chart.series || seriesCount === 0) return
|
|
@@ -58,12 +139,15 @@ const echartsOption = computed(() => {
|
|
|
58
139
|
return null
|
|
59
140
|
}
|
|
60
141
|
|
|
61
|
-
const
|
|
62
|
-
const
|
|
142
|
+
const rawData = props.series.data[resourceId]
|
|
143
|
+
const sortedData = isDateAxis.value
|
|
144
|
+
? rawData.map(row => parseXValueAsDate(row, xColumn))
|
|
145
|
+
: [...rawData]
|
|
146
|
+
const sortBy = props.chart.x_axis.sort_x_by ?? (isDateAxis.value ? 'axis_x' : null)
|
|
63
147
|
const sortDirection = props.chart.x_axis.sort_x_direction ?? 'asc'
|
|
64
148
|
|
|
65
149
|
if (sortBy && sortDirection && props.chart.x_axis.column_x) {
|
|
66
|
-
const sortKey = sortBy === '
|
|
150
|
+
const sortKey = sortBy === 'axis_y' ? yColumn : xColumn
|
|
67
151
|
sortedData.sort((a, b) => {
|
|
68
152
|
const valA = a[sortKey]
|
|
69
153
|
const valB = b[sortKey]
|
|
@@ -136,6 +220,8 @@ const echartsOption = computed(() => {
|
|
|
136
220
|
seriesData.data.push(curr.data)
|
|
137
221
|
}
|
|
138
222
|
|
|
223
|
+
const xAxisType = mapXAxisType(props.chart.x_axis)
|
|
224
|
+
|
|
139
225
|
return {
|
|
140
226
|
dataset: [...seriesData.data],
|
|
141
227
|
textStyle: {
|
|
@@ -146,10 +232,12 @@ const echartsOption = computed(() => {
|
|
|
146
232
|
trigger: 'axis' as const,
|
|
147
233
|
formatter: (params: Array<{ value: Record<string, unknown>, axisValueLabel: string, seriesName: string }>) => {
|
|
148
234
|
let tooltip = ''
|
|
149
|
-
const
|
|
235
|
+
const valueFormatter = new Intl.NumberFormat(locale)
|
|
236
|
+
const xAxisFormatter = buildXAxisFormatter(xAxisColumn.value, { dateStyle: 'medium' })
|
|
150
237
|
for (const param of params) {
|
|
151
238
|
const seriesName = param.seriesName
|
|
152
|
-
|
|
239
|
+
const axisLabel = xAxisFormatter ? xAxisFormatter(param.axisValueLabel) : param.axisValueLabel
|
|
240
|
+
tooltip += `${format.encodeHTML(String(axisLabel))}: <strong>${valueFormatter.format(Number(param.value[seriesName]))}</strong><br>`
|
|
153
241
|
}
|
|
154
242
|
return tooltip
|
|
155
243
|
},
|
|
@@ -166,8 +254,13 @@ const echartsOption = computed(() => {
|
|
|
166
254
|
containLabel: true,
|
|
167
255
|
},
|
|
168
256
|
xAxis: {
|
|
169
|
-
type:
|
|
257
|
+
type: xAxisType,
|
|
170
258
|
name: (props.chart.x_axis as XAxis).column_x,
|
|
259
|
+
min: xAxisType !== 'category' ? xAxisColumn.value?.min ?? xAxisBounds.value.min ?? undefined : undefined,
|
|
260
|
+
max: xAxisType !== 'category' ? xAxisColumn.value?.max ?? xAxisBounds.value.max ?? undefined : undefined,
|
|
261
|
+
axisLabel: xAxisType === 'time'
|
|
262
|
+
? { formatter: buildXAxisFormatter(xAxisColumn.value) }
|
|
263
|
+
: undefined,
|
|
171
264
|
},
|
|
172
265
|
yAxis: {
|
|
173
266
|
type: 'value' as const,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
>
|
|
6
6
|
<template #default="{ data }">
|
|
7
7
|
<ChartViewer
|
|
8
|
+
ref="chartViewerRef"
|
|
8
9
|
:chart="chart"
|
|
9
10
|
:series="data"
|
|
10
11
|
/>
|
|
@@ -33,6 +34,13 @@ const props = defineProps<{
|
|
|
33
34
|
}>()
|
|
34
35
|
|
|
35
36
|
const { t } = useTranslation()
|
|
37
|
+
const chartViewerRef = ref<InstanceType<typeof ChartViewer> | null>(null)
|
|
38
|
+
|
|
39
|
+
function capture(): string | null {
|
|
40
|
+
return chartViewerRef.value?.capture() ?? null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defineExpose({ capture })
|
|
36
44
|
const config = useComponentsConfig()
|
|
37
45
|
const getProfile = useGetProfile()
|
|
38
46
|
|
|
@@ -99,6 +99,11 @@
|
|
|
99
99
|
</p>
|
|
100
100
|
</div>
|
|
101
101
|
</div>
|
|
102
|
+
<DataserviceQualityInline
|
|
103
|
+
v-if="dataservice.quality"
|
|
104
|
+
:quality="dataservice.quality"
|
|
105
|
+
/>
|
|
106
|
+
|
|
102
107
|
<ObjectCardShortDescription
|
|
103
108
|
v-if="showDescription"
|
|
104
109
|
:text="dataservice.description"
|
|
@@ -126,6 +131,7 @@ import ObjectCardBadge from './ObjectCardBadge.vue'
|
|
|
126
131
|
import ObjectCardHeader from './ObjectCardHeader.vue'
|
|
127
132
|
import ObjectCardOwner from './ObjectCardOwner.vue'
|
|
128
133
|
import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
|
|
134
|
+
import DataserviceQualityInline from './DataserviceQualityInline.vue'
|
|
129
135
|
|
|
130
136
|
type Props = {
|
|
131
137
|
dataservice: Dataservice
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Toggletip
|
|
3
|
+
:styled-button="false"
|
|
4
|
+
button-class="flex items-center gap-1 rounded-sm p-1.5 -m-1.5 border-transparent -outline-offset-2 hover:bg-gray-some transition-colors w-full text-left"
|
|
5
|
+
:button-props="{ title: t('Qualité des métadonnées') }"
|
|
6
|
+
>
|
|
7
|
+
<QualityScore
|
|
8
|
+
:score="quality.score"
|
|
9
|
+
class="w-full"
|
|
10
|
+
/>
|
|
11
|
+
<RiInformationLine
|
|
12
|
+
class="size-5 shrink-0"
|
|
13
|
+
aria-hidden="true"
|
|
14
|
+
/>
|
|
15
|
+
<template #toggletip>
|
|
16
|
+
<DataserviceQualityTooltipContent :quality />
|
|
17
|
+
</template>
|
|
18
|
+
</Toggletip>
|
|
19
|
+
<template v-if="!hideWarnings">
|
|
20
|
+
<ul class="list-none pl-0">
|
|
21
|
+
<QualityItemWarning
|
|
22
|
+
:quality-item="quality.has_description"
|
|
23
|
+
:message="t('Description non renseignée')"
|
|
24
|
+
/>
|
|
25
|
+
<QualityItemWarning
|
|
26
|
+
:quality-item="quality.has_machine_documentation"
|
|
27
|
+
:message="t('Documentation machine (OpenAPI) manquante')"
|
|
28
|
+
/>
|
|
29
|
+
<QualityItemWarning
|
|
30
|
+
:quality-item="quality.has_technical_documentation"
|
|
31
|
+
:message="t('Documentation technique manquante')"
|
|
32
|
+
/>
|
|
33
|
+
<QualityItemWarning
|
|
34
|
+
:quality-item="quality.has_business_documentation"
|
|
35
|
+
:message="t('Documentation métier manquante')"
|
|
36
|
+
/>
|
|
37
|
+
<QualityItemWarning
|
|
38
|
+
:quality-item="quality.license"
|
|
39
|
+
:message="t('Licence non renseignée')"
|
|
40
|
+
/>
|
|
41
|
+
<QualityItemWarning
|
|
42
|
+
:quality-item="quality.has_contact_point"
|
|
43
|
+
:message="t('Point de contact non renseigné')"
|
|
44
|
+
/>
|
|
45
|
+
<QualityItemWarning
|
|
46
|
+
:quality-item="quality.has_base_api_url"
|
|
47
|
+
:message="t(`URL de base de l'API non renseignée`)"
|
|
48
|
+
/>
|
|
49
|
+
<QualityItemWarning
|
|
50
|
+
:quality-item="quality.availability_documented"
|
|
51
|
+
:message="t('Disponibilité non documentée')"
|
|
52
|
+
/>
|
|
53
|
+
<QualityItemWarning
|
|
54
|
+
:quality-item="quality.rate_limiting_documented"
|
|
55
|
+
:message="t(`Limite d'appels non documentée`)"
|
|
56
|
+
/>
|
|
57
|
+
<QualityItemWarning
|
|
58
|
+
:quality-item="quality.access_conditions_clear"
|
|
59
|
+
:message="t(`Conditions d'accès non précisées`)"
|
|
60
|
+
/>
|
|
61
|
+
</ul>
|
|
62
|
+
</template>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script setup lang="ts">
|
|
66
|
+
import { RiInformationLine } from '@remixicon/vue'
|
|
67
|
+
import type { DataserviceQuality } from '../types/dataservices'
|
|
68
|
+
import QualityScore from './QualityScore.vue'
|
|
69
|
+
import QualityItemWarning from './QualityItemWarning.vue'
|
|
70
|
+
import DataserviceQualityTooltipContent from './DataserviceQualityTooltipContent.vue'
|
|
71
|
+
import Toggletip from './Toggletip.vue'
|
|
72
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
73
|
+
|
|
74
|
+
withDefaults(defineProps<{
|
|
75
|
+
quality: DataserviceQuality
|
|
76
|
+
hideWarnings?: boolean
|
|
77
|
+
}>(), {
|
|
78
|
+
hideWarnings: false,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const { t } = useTranslation()
|
|
82
|
+
</script>
|
|
@@ -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>
|
|
@@ -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
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="text-xs">
|
|
3
|
+
<PreviewUnavailable v-if="!isSizeAllowed">
|
|
4
|
+
{{ fileSizeBytes
|
|
5
|
+
? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.", { fileType })
|
|
6
|
+
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
|
|
7
|
+
}}
|
|
8
|
+
</PreviewUnavailable>
|
|
9
|
+
<PreviewUnavailable v-else-if="hasError">
|
|
10
|
+
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
|
|
11
|
+
</PreviewUnavailable>
|
|
12
|
+
<img
|
|
13
|
+
v-else
|
|
14
|
+
:src="resource.url"
|
|
15
|
+
alt=""
|
|
16
|
+
class="block w-full h-auto max-w-[800px]"
|
|
17
|
+
@error="hasError = true"
|
|
18
|
+
>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { computed, ref } from 'vue'
|
|
24
|
+
import PreviewUnavailable from './PreviewUnavailable.vue'
|
|
25
|
+
import type { Resource } from '../../types/resources'
|
|
26
|
+
import { getResourceFilesize } from '../../functions/resources'
|
|
27
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
28
|
+
import { useComponentsConfig } from '../../config'
|
|
29
|
+
|
|
30
|
+
const props = defineProps<{
|
|
31
|
+
resource: Resource
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const { t } = useTranslation()
|
|
35
|
+
const config = useComponentsConfig()
|
|
36
|
+
|
|
37
|
+
const hasError = ref(false)
|
|
38
|
+
|
|
39
|
+
const fileType = computed(() => props.resource.format?.toUpperCase() ?? t('Fichier'))
|
|
40
|
+
const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
|
|
41
|
+
|
|
42
|
+
const isSizeAllowed = computed(() => {
|
|
43
|
+
const size = fileSizeBytes.value
|
|
44
|
+
const max = config.maxImagePreviewByteSize
|
|
45
|
+
if (!size || !max) return false
|
|
46
|
+
return size <= max
|
|
47
|
+
})
|
|
48
|
+
</script>
|
|
@@ -224,6 +224,10 @@
|
|
|
224
224
|
v-else-if="resource.format && resource.format.toLowerCase() === 'xml'"
|
|
225
225
|
:resource="resource"
|
|
226
226
|
/>
|
|
227
|
+
<ImagePreview
|
|
228
|
+
v-else-if="isImagePreviewFormat(resource.format)"
|
|
229
|
+
:resource="resource"
|
|
230
|
+
/>
|
|
227
231
|
<!-- Show Datafair embedded preview (koumoul) -->
|
|
228
232
|
<DatafairPreview
|
|
229
233
|
v-else-if="hasDatafairPreview"
|
|
@@ -309,7 +313,7 @@ import { trackEvent } from '../../functions/matomo'
|
|
|
309
313
|
import CopyButton from '../CopyButton.vue'
|
|
310
314
|
import { useComponentsConfig } from '../../config'
|
|
311
315
|
import { getOwnerName } from '../../functions/owned'
|
|
312
|
-
import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize } from '../../functions/resources'
|
|
316
|
+
import { getResourceFormatIcon, getResourceTitleId, detectOgcService, getResourceExternalUrl, getResourceFilesize, isImagePreviewFormat } from '../../functions/resources'
|
|
313
317
|
import BrandedButton from '../BrandedButton.vue'
|
|
314
318
|
import { useTranslation } from '../../composables/useTranslation'
|
|
315
319
|
import { useHasTabularData } from '../../composables/useHasTabularData'
|
|
@@ -344,6 +348,7 @@ const Pmtiles = defineAsyncComponent(() => import('./Pmtiles.client.vue'))
|
|
|
344
348
|
const JsonPreview = defineAsyncComponent(() => import('./JsonPreview.client.vue'))
|
|
345
349
|
const PdfPreview = defineAsyncComponent(() => import('./PdfPreview.client.vue'))
|
|
346
350
|
const XmlPreview = defineAsyncComponent(() => import('./XmlPreview.client.vue'))
|
|
351
|
+
const ImagePreview = defineAsyncComponent(() => import('./ImagePreview.client.vue'))
|
|
347
352
|
const DatafairPreview = defineAsyncComponent(() => import('./Datafair.client.vue'))
|
|
348
353
|
|
|
349
354
|
const { t } = useTranslation()
|
|
@@ -351,11 +356,11 @@ const { formatRelativeIfRecentDate } = useFormatDate()
|
|
|
351
356
|
const checkTabularData = useHasTabularData()
|
|
352
357
|
|
|
353
358
|
const hasPreview = computed(() => {
|
|
354
|
-
// For JSON, PDF, and
|
|
359
|
+
// For JSON, PDF, XML, and image files, show preview.
|
|
355
360
|
// We cannot check for CORS issues here because we cannot use an async component here.
|
|
356
361
|
// If there is a CORS issue when fetching the file for preview, it will be managed and displayed as an error banner by the preview component.
|
|
357
362
|
const format = props.resource.format?.toLowerCase()
|
|
358
|
-
return format === 'json' || format === 'pdf' || format === 'xml'
|
|
363
|
+
return format === 'json' || format === 'pdf' || format === 'xml' || isImagePreviewFormat(format)
|
|
359
364
|
})
|
|
360
365
|
|
|
361
366
|
const hasTabularData = computed(() => checkTabularData(props.resource))
|