@datagouv/components-next 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/{Control-ZFh5ta_U.js → Control-b-43UT2B.js} +10 -10
- package/dist/{Datafair.client-rf4T1IkA.js → Datafair.client-BrjISlaZ.js} +4 -4
- package/dist/{Event-DSQcW7OF.js → Event-py_AAwJY.js} +18 -18
- package/dist/{Image-BijNEG0p.js → Image-BHQYZWzH.js} +23 -23
- package/dist/{JsonPreview.client-dzar6iuh.js → JsonPreview.client-DivFAnbN.js} +5 -5
- package/dist/{Map-BUtPf5GN.js → Map-CtP7leHl.js} +1313 -1173
- package/dist/{MapContainer.client-D-MoRNhG.js → MapContainer.client-Cq8tNLZZ.js} +17 -17
- package/dist/{OSM-D4MTdBtk.js → OSM-gdDSIsDA.js} +15 -23
- package/dist/{PdfPreview.client-DoDYLmJD.js → PdfPreview.client-BNBgLsK2.js} +6 -10
- package/dist/Pmtiles.client-CQi5Rw8W.js +26123 -0
- package/dist/{PreviewWrapper.vue_vue_type_script_setup_true_lang-BRNYswg3.js → PreviewWrapper.vue_vue_type_script_setup_true_lang-C3dDzrIr.js} +2 -2
- package/dist/{ScaleLine-hJQIqcZm.js → ScaleLine-hG-FhNqy.js} +2 -2
- package/dist/{Tile-Dcl7oIVu.js → Tile-D6rFonpT.js} +163 -166
- package/dist/{TileImage-BJeHipMX.js → TileImage-cfrEgz5y.js} +131 -119
- package/dist/{View-xp_P_OHw.js → View-vp7vDb9q.js} +169 -119
- package/dist/{XmlPreview.client-cOhwff6P.js → XmlPreview.client-XCsNvnsZ.js} +5 -5
- package/dist/{common-BjQlan3k.js → common-C8efyyuc.js} +49 -49
- package/dist/components-next.css +1 -52
- package/dist/components-next.js +157 -153
- package/dist/components.css +2 -2
- package/dist/{index-NofRBuyf.js → index-CXUamc8Z.js} +6382 -6057
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-BRKv9Ksv.js} +162 -164
- package/dist/{main-Iz1ZCL6k.js → main-DKu5SOAl.js} +20671 -21654
- package/dist/{proj-CsNo9yH1.js → proj-CsjVeojN.js} +322 -306
- package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
- package/dist/{tilecoord-A0fLnBZr.js → tilecoord-CPcQJnrF.js} +270 -261
- package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
- package/dist/{vue3-xml-viewer.common-tVI9uXUz.js → vue3-xml-viewer.common-BIaEIFYG.js} +1 -1
- package/package.json +34 -34
- package/src/components/ResourceAccordion/Pmtiles.client.vue +3 -1
- package/src/components/ResourceAccordion/ResourceAccordion.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorer.vue +8 -9
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +4 -1
- package/src/components/Search/GlobalSearch.vue +16 -2
- package/src/components/TabularExplorer/TabularCellPopover.vue +2 -2
- package/src/components/TabularExplorer/TabularExplorer.vue +212 -74
- package/src/components/TabularExplorer/TabularFilterContent.vue +15 -13
- package/src/components/TabularExplorer/TabularFilterPopover.vue +21 -26
- package/src/components/TabularExplorer/types.ts +9 -9
- package/src/composables/useResourceCapabilities.ts +1 -1
- package/src/composables/useSearchFilter.ts +7 -5
- package/src/functions/api.ts +13 -7
- package/src/functions/resources.ts +5 -0
- package/src/functions/tabular.ts +103 -1
- package/src/types/resources.ts +0 -1
- package/dist/Pmtiles.client-Dzm01Zfm.js +0 -25185
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Response from /api/resources/{rid}/data/ */
|
|
2
|
-
export
|
|
2
|
+
export type TabularDataResponse = {
|
|
3
3
|
data: TabularRow[]
|
|
4
4
|
meta: {
|
|
5
5
|
page: number
|
|
@@ -17,11 +17,11 @@ export interface TabularDataResponse {
|
|
|
17
17
|
export type TabularRow = Record<string, unknown> & { __id: number }
|
|
18
18
|
|
|
19
19
|
/** Response from /api/resources/{rid}/profile/ */
|
|
20
|
-
export
|
|
20
|
+
export type TabularProfileResponse = {
|
|
21
21
|
profile: TabularProfile
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export
|
|
24
|
+
export type TabularProfile = {
|
|
25
25
|
header: string[]
|
|
26
26
|
columns: Record<string, TabularColumnInfo>
|
|
27
27
|
formats: Record<string, string[]>
|
|
@@ -38,13 +38,13 @@ export interface TabularProfile {
|
|
|
38
38
|
trailing_columns: number
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export
|
|
41
|
+
export type TabularColumnInfo = {
|
|
42
42
|
score: number
|
|
43
43
|
format: string
|
|
44
44
|
python_type: string
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export
|
|
47
|
+
export type TabularColumnProfile = {
|
|
48
48
|
tops: TabularTopValue[]
|
|
49
49
|
nb_distinct: number
|
|
50
50
|
nb_missing_values: number
|
|
@@ -54,14 +54,14 @@ export interface TabularColumnProfile {
|
|
|
54
54
|
mean?: number
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export
|
|
57
|
+
export type TabularTopValue = {
|
|
58
58
|
value: string
|
|
59
59
|
count: number
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export type ColumnType = 'number' | 'categorical' | 'text' | 'date' | 'boolean'
|
|
63
63
|
|
|
64
|
-
export
|
|
64
|
+
export type ColumnFilters = {
|
|
65
65
|
in?: string[]
|
|
66
66
|
exact?: string
|
|
67
67
|
min?: number
|
|
@@ -72,12 +72,12 @@ export interface ColumnFilters {
|
|
|
72
72
|
|
|
73
73
|
export type SortDirection = 'asc' | 'desc'
|
|
74
74
|
|
|
75
|
-
export
|
|
75
|
+
export type SortConfig = {
|
|
76
76
|
column: string
|
|
77
77
|
direction: SortDirection
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
export
|
|
80
|
+
export type BadgeStyle = {
|
|
81
81
|
backgroundColor: string
|
|
82
82
|
color: string
|
|
83
83
|
}
|
|
@@ -108,7 +108,7 @@ export function useResourceCapabilities(
|
|
|
108
108
|
options.push({ key: 'downloads', label: t('Téléchargements') })
|
|
109
109
|
|
|
110
110
|
if (hasTabularData.value) {
|
|
111
|
-
options.push({ key: '
|
|
111
|
+
options.push({ key: 'api', label: t('API') })
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
return options
|
|
@@ -94,10 +94,12 @@ export function useSearchFilter(
|
|
|
94
94
|
})
|
|
95
95
|
|
|
96
96
|
onScopeDispose(() => {
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
97
|
+
// Type-scoped filters (typeKeys set) are cleared by GlobalSearch's
|
|
98
|
+
// watch(currentType) instead, so their URL update batches with the other
|
|
99
|
+
// type-change resets via useRouteQuery rather than racing them with the
|
|
100
|
+
// separate router.replace below. Here we only clear filters that apply to
|
|
101
|
+
// all types: their unmount is a consumer signal unrelated to a type switch,
|
|
102
|
+
// which GlobalSearch has no way to detect.
|
|
101
103
|
//
|
|
102
104
|
// We cannot use `value.value = defaultValue` here because VueUse's
|
|
103
105
|
// useRouteQuery registers its own tryOnScopeDispose cleanup that zeroes
|
|
@@ -105,7 +107,7 @@ export function useSearchFilter(
|
|
|
105
107
|
// The setter then sees `query === v` and early-returns without ever
|
|
106
108
|
// calling router.replace(). Instead we read the live route.query directly
|
|
107
109
|
// (which is router state, not affected by that cleanup) and push the update.
|
|
108
|
-
if (route.query[urlParam] !== undefined) {
|
|
110
|
+
if (normalizedTypeKeys === undefined && route.query[urlParam] !== undefined) {
|
|
109
111
|
const { [urlParam]: _removed, ...restQuery } = route.query
|
|
110
112
|
router.replace({
|
|
111
113
|
query: defaultValue === undefined ? restQuery : { ...restQuery, [urlParam]: String(defaultValue) },
|
package/src/functions/api.ts
CHANGED
|
@@ -26,7 +26,7 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
26
26
|
const error: Ref<ErrorT | null> = ref(null)
|
|
27
27
|
const status = ref<AsyncDataRequestStatus>('idle')
|
|
28
28
|
|
|
29
|
-
const execute = async () => {
|
|
29
|
+
const execute = async (signal?: AbortSignal) => {
|
|
30
30
|
const urlValue = toValue(url)
|
|
31
31
|
if (!urlValue) return
|
|
32
32
|
const params = deepToValue(options?.params)
|
|
@@ -36,13 +36,15 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
36
36
|
data.value = isRaw
|
|
37
37
|
// Raw targets another data.gouv service (the Tabular API in TabularExplorer) via its own
|
|
38
38
|
// absolute URL, so it must stay bare ofetch: no datagouv apiBase, no datagouv auth attached.
|
|
39
|
-
? await ofetch<DataT | null>(urlValue, { params: params ?? query })
|
|
39
|
+
? await ofetch<DataT | null>(urlValue, { params: params ?? query, signal })
|
|
40
40
|
// The configured `$fetch` carries baseURL + auth + headers (see the `datagouv` plugin install
|
|
41
41
|
// for the default one). We only forward the URL and params here.
|
|
42
|
-
: await config.$fetch<DataT | null>(urlValue, { baseURL: config.apiBase, params: params ?? query })
|
|
42
|
+
: await config.$fetch<DataT | null>(urlValue, { baseURL: config.apiBase, params: params ?? query, signal })
|
|
43
43
|
status.value = 'success'
|
|
44
44
|
}
|
|
45
45
|
catch (e) {
|
|
46
|
+
// Ignore aborted requests — they're superseded by a newer call, not a real error.
|
|
47
|
+
if (signal?.aborted) return
|
|
46
48
|
error.value = e as ErrorT
|
|
47
49
|
status.value = 'error'
|
|
48
50
|
}
|
|
@@ -51,14 +53,18 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
51
53
|
// When server is false, only start watching after mount (client-side only)
|
|
52
54
|
if (options?.server === false) {
|
|
53
55
|
onMounted(() => {
|
|
54
|
-
watchEffect(
|
|
55
|
-
|
|
56
|
+
watchEffect((onCleanup) => {
|
|
57
|
+
const controller = new AbortController()
|
|
58
|
+
onCleanup(() => controller.abort())
|
|
59
|
+
void execute(controller.signal)
|
|
56
60
|
})
|
|
57
61
|
})
|
|
58
62
|
}
|
|
59
63
|
else {
|
|
60
|
-
watchEffect(
|
|
61
|
-
|
|
64
|
+
watchEffect((onCleanup) => {
|
|
65
|
+
const controller = new AbortController()
|
|
66
|
+
onCleanup(() => controller.abort())
|
|
67
|
+
void execute(controller.signal)
|
|
62
68
|
})
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -91,6 +91,11 @@ export function getResourceTitleId(resource: Resource) {
|
|
|
91
91
|
export const RESOURCE_TYPE = readonly(['main', 'documentation', 'update', 'api', 'code', 'other'] as const)
|
|
92
92
|
export type ResourceType = typeof RESOURCE_TYPE[number]
|
|
93
93
|
|
|
94
|
+
// Page size used by ResourceExplorer for its per-type resource fetches.
|
|
95
|
+
// Exported so consumers (e.g. the dataset page's `main` prefetch) can build a
|
|
96
|
+
// byte-for-byte identical cache key and let Nuxt dedupe the request.
|
|
97
|
+
export const RESOURCE_EXPLORER_PAGE_SIZE = 10
|
|
98
|
+
|
|
94
99
|
export const getResourceLabel = (type: ResourceType, count?: number) => {
|
|
95
100
|
const { t } = useTranslation()
|
|
96
101
|
switch (type) {
|
package/src/functions/tabular.ts
CHANGED
|
@@ -4,9 +4,24 @@ import {
|
|
|
4
4
|
RiPriceTag3Line,
|
|
5
5
|
RiText,
|
|
6
6
|
RiCalendarLine,
|
|
7
|
+
RiCalendarEventLine,
|
|
8
|
+
RiTimeLine,
|
|
7
9
|
RiCheckboxLine,
|
|
10
|
+
RiMapPin2Line,
|
|
11
|
+
RiGlobalLine,
|
|
12
|
+
RiFocus3Line,
|
|
13
|
+
RiFingerprintLine,
|
|
14
|
+
RiMailLine,
|
|
15
|
+
RiLinksLine,
|
|
16
|
+
RiPhoneLine,
|
|
17
|
+
RiUserLine,
|
|
18
|
+
RiMoneyEuroCircleLine,
|
|
19
|
+
RiPercentLine,
|
|
20
|
+
RiBracesLine,
|
|
21
|
+
RiCodeLine,
|
|
8
22
|
} from '@remixicon/vue'
|
|
9
23
|
import { useTranslation } from '../composables/useTranslation'
|
|
24
|
+
import type { TranslationFunction } from '../composables/useTranslation'
|
|
10
25
|
import type { ColumnFilters, ColumnType } from '../components/TabularExplorer/types'
|
|
11
26
|
|
|
12
27
|
export function hasFilterForColumn(filters: Record<string, ColumnFilters>, column: string): boolean {
|
|
@@ -15,7 +30,12 @@ export function hasFilterForColumn(filters: Record<string, ColumnFilters>, colum
|
|
|
15
30
|
return !!(f.in?.length || f.exact != null || f.contains || f.null || f.min != null || f.max != null)
|
|
16
31
|
}
|
|
17
32
|
|
|
18
|
-
export
|
|
33
|
+
export type TypeDisplay = {
|
|
34
|
+
icon: Component
|
|
35
|
+
label: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function buildTypeConfig(t: TranslationFunction): Record<ColumnType, TypeDisplay> {
|
|
19
39
|
return {
|
|
20
40
|
number: { icon: RiHashtag, label: t('Nombre') },
|
|
21
41
|
categorical: { icon: RiPriceTag3Line, label: t('Catégoriel') },
|
|
@@ -25,6 +45,88 @@ export function buildTypeConfig(t: (s: string) => string): Record<ColumnType, {
|
|
|
25
45
|
}
|
|
26
46
|
}
|
|
27
47
|
|
|
48
|
+
// csv-detective formats that carry no semantic meaning beyond the generic
|
|
49
|
+
// display type already resolved by `ColumnType` — these fall back to
|
|
50
|
+
// `buildTypeConfig` (Texte / Nombre / Date / Booléen) in the header.
|
|
51
|
+
export const GENERIC_FORMATS = new Set(['string', 'int', 'float', 'date', 'bool', 'booleen'])
|
|
52
|
+
|
|
53
|
+
// Semantic types detected by csv-detective (see ../../../csv-detective/csv_detective/formats).
|
|
54
|
+
// The list is intentionally exhaustive, but unknown formats are tolerated:
|
|
55
|
+
// `getColumnDisplay` humanizes any format missing from this map.
|
|
56
|
+
export function buildFormatConfig(t: TranslationFunction): Record<string, TypeDisplay> {
|
|
57
|
+
return {
|
|
58
|
+
// Geo — territories & addresses
|
|
59
|
+
adresse: { icon: RiMapPin2Line, label: t('Adresse') },
|
|
60
|
+
commune: { icon: RiMapPin2Line, label: t('Commune') },
|
|
61
|
+
code_commune: { icon: RiMapPin2Line, label: t('Code INSEE commune') },
|
|
62
|
+
code_commune_insee: { icon: RiMapPin2Line, label: t('Code INSEE commune') },
|
|
63
|
+
departement: { icon: RiMapPin2Line, label: t('Département') },
|
|
64
|
+
code_departement: { icon: RiMapPin2Line, label: t('Code département') },
|
|
65
|
+
region: { icon: RiMapPin2Line, label: t('Région') },
|
|
66
|
+
code_region: { icon: RiMapPin2Line, label: t('Code région') },
|
|
67
|
+
code_epci: { icon: RiMapPin2Line, label: t('Code EPCI') },
|
|
68
|
+
code_postal: { icon: RiMapPin2Line, label: t('Code postal') },
|
|
69
|
+
insee_canton: { icon: RiMapPin2Line, label: t('Canton') },
|
|
70
|
+
code_fantoir: { icon: RiMapPin2Line, label: t('Code FANTOIR') },
|
|
71
|
+
id_rnb: { icon: RiMapPin2Line, label: t('Identifiant RNB') },
|
|
72
|
+
// Geo — countries
|
|
73
|
+
pays: { icon: RiGlobalLine, label: t('Pays') },
|
|
74
|
+
iso_country_code_alpha2: { icon: RiGlobalLine, label: t('Code pays (ISO α-2)') },
|
|
75
|
+
iso_country_code_alpha3: { icon: RiGlobalLine, label: t('Code pays (ISO α-3)') },
|
|
76
|
+
iso_country_code_numeric: { icon: RiGlobalLine, label: t('Code pays (ISO num.)') },
|
|
77
|
+
// Geo — coordinates
|
|
78
|
+
latitude_wgs: { icon: RiFocus3Line, label: t('Latitude') },
|
|
79
|
+
latitude_wgs_fr_metropole: { icon: RiFocus3Line, label: t('Latitude') },
|
|
80
|
+
longitude_wgs: { icon: RiFocus3Line, label: t('Longitude') },
|
|
81
|
+
longitude_wgs_fr_metropole: { icon: RiFocus3Line, label: t('Longitude') },
|
|
82
|
+
latitude_l93: { icon: RiFocus3Line, label: t('Latitude (Lambert 93)') },
|
|
83
|
+
longitude_l93: { icon: RiFocus3Line, label: t('Longitude (Lambert 93)') },
|
|
84
|
+
latlon_wgs: { icon: RiFocus3Line, label: t('Latitude / longitude') },
|
|
85
|
+
lonlat_wgs: { icon: RiFocus3Line, label: t('Longitude / latitude') },
|
|
86
|
+
// Identifiers
|
|
87
|
+
siren: { icon: RiFingerprintLine, label: t('SIREN') },
|
|
88
|
+
siret: { icon: RiFingerprintLine, label: t('SIRET') },
|
|
89
|
+
code_rna: { icon: RiFingerprintLine, label: t('Identifiant RNA') },
|
|
90
|
+
code_waldec: { icon: RiFingerprintLine, label: t('Identifiant WALDEC') },
|
|
91
|
+
code_import: { icon: RiFingerprintLine, label: t('Code Import') },
|
|
92
|
+
uai: { icon: RiFingerprintLine, label: t('UAI') },
|
|
93
|
+
insee_ape700: { icon: RiFingerprintLine, label: t('Code APE') },
|
|
94
|
+
code_csp_insee: { icon: RiFingerprintLine, label: t('Code CSP') },
|
|
95
|
+
uuid: { icon: RiFingerprintLine, label: t('UUID') },
|
|
96
|
+
mongo_object_id: { icon: RiFingerprintLine, label: t('Identifiant Mongo') },
|
|
97
|
+
// Contact
|
|
98
|
+
email: { icon: RiMailLine, label: t('Email') },
|
|
99
|
+
url: { icon: RiLinksLine, label: t('URL') },
|
|
100
|
+
tel_fr: { icon: RiPhoneLine, label: t('Téléphone') },
|
|
101
|
+
username: { icon: RiUserLine, label: t('Nom d\'utilisateur') },
|
|
102
|
+
// People
|
|
103
|
+
sexe: { icon: RiUserLine, label: t('Sexe') },
|
|
104
|
+
csp_insee: { icon: RiUserLine, label: t('Catégorie socio-pro.') },
|
|
105
|
+
// Quantities
|
|
106
|
+
money: { icon: RiMoneyEuroCircleLine, label: t('Montant') },
|
|
107
|
+
percent: { icon: RiPercentLine, label: t('Pourcentage') },
|
|
108
|
+
// Temporal
|
|
109
|
+
year: { icon: RiCalendarLine, label: t('Année') },
|
|
110
|
+
date_fr: { icon: RiCalendarLine, label: t('Date') },
|
|
111
|
+
jour_de_la_semaine: { icon: RiCalendarEventLine, label: t('Jour de la semaine') },
|
|
112
|
+
mois_de_lannee: { icon: RiCalendarEventLine, label: t('Mois de l\'année') },
|
|
113
|
+
datetime_naive: { icon: RiTimeLine, label: t('Date et heure') },
|
|
114
|
+
datetime_aware: { icon: RiTimeLine, label: t('Date et heure') },
|
|
115
|
+
datetime_rfc822: { icon: RiTimeLine, label: t('Date et heure') },
|
|
116
|
+
// Structured
|
|
117
|
+
json: { icon: RiBracesLine, label: t('JSON') },
|
|
118
|
+
geojson: { icon: RiBracesLine, label: t('GeoJSON') },
|
|
119
|
+
binary: { icon: RiCodeLine, label: t('Binaire') },
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Tolerant fallback for a semantic format we don't explicitly know about:
|
|
124
|
+
// "code_truc_machin" -> "Code truc machin".
|
|
125
|
+
export function humanizeFormat(format: string): string {
|
|
126
|
+
const spaced = format.replace(/_/g, ' ').trim()
|
|
127
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1)
|
|
128
|
+
}
|
|
129
|
+
|
|
28
130
|
export function useFormatTabular() {
|
|
29
131
|
const { locale } = useTranslation()
|
|
30
132
|
|
package/src/types/resources.ts
CHANGED