@datagouv/components-next 1.0.2-dev.172 → 1.0.2-dev.174
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-CES67S3v.js → Datafair.client-BU2gspr8.js} +9 -9
- package/dist/ImagePreview.client-BU5QNqG_.js +36 -0
- package/dist/{JsonPreview.client-DtCw5aCA.js → JsonPreview.client-CvOpMCxW.js} +2 -2
- package/dist/{MapContainer.client-Q3TmXpPR.js → MapContainer.client-DNOQ5XTT.js} +9 -9
- package/dist/{PdfPreview.client-BXJzS-Hm.js → PdfPreview.client-DwAvLLq_.js} +438 -427
- package/dist/{Pmtiles.client-DNiYcHH3.js → Pmtiles.client-CgaDrCHk.js} +1341 -1337
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-CrWEJnPl.js +60 -0
- package/dist/{XmlPreview.client-CPfIZl9q.js → XmlPreview.client-B24FIhU7.js} +3 -3
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +144 -138
- package/dist/components.css +1 -1
- package/dist/{index-CXeG6J6C.js → index-Dw1lR0oe.js} +1 -1
- package/dist/{leaflet-src-BwEce6uO.js → leaflet-src-BoMqG6Yx.js} +1 -1
- package/dist/{main-D7EaQcIR.js → main-I9G-L-lg.js} +16280 -15511
- package/dist/{vue3-xml-viewer.common-DznVepCg.js → vue3-xml-viewer.common-PTUJZpH4.js} +1 -1
- package/package.json +1 -1
- package/src/components/AppLink.vue +3 -0
- package/src/components/CopyButton.vue +6 -2
- package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
- package/src/components/ResourceAccordion/ImagePreview.client.vue +3 -3
- package/src/components/ResourceAccordion/MapContainer.client.vue +1 -8
- package/src/components/ResourceAccordion/PdfPreview.client.vue +36 -12
- package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
- package/src/components/ResourceAccordion/Pmtiles.client.vue +25 -12
- package/src/components/ResourceAccordion/PreviewWrapper.vue +17 -10
- package/src/components/ResourceAccordion/ResourceAccordion.vue +4 -1
- package/src/components/ResourceExplorer/ResourceExplorer.vue +98 -209
- package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +74 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +103 -71
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +123 -187
- package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
- package/src/components/ResourceExplorer/ResourceSelector.vue +24 -86
- package/src/components/ResourceExplorer/ResourceViewerHeader.vue +115 -0
- package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +94 -0
- package/src/components/ResourceListItem.vue +154 -0
- package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
- package/src/components/TabularExplorer/TabularCell.vue +2 -4
- package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +68 -995
- package/src/components/TabularExplorer/TabularFilterContent.vue +2 -10
- package/src/components/TabularExplorer/TabularFilterPopover.vue +5 -6
- 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 +0 -5
- package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
- package/src/components/TabularExplorer/useTabularContext.ts +82 -0
- package/src/composables/useDatasetResources.ts +164 -0
- package/src/composables/useResizable.ts +46 -0
- package/src/composables/useResourceCapabilities.ts +20 -7
- package/src/functions/resources.ts +19 -0
- package/src/main.ts +8 -0
- package/dist/ImagePreview.client-c_SJXLpr.js +0 -36
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-B3ByRwHK.js +0 -58
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
:is="config?.appLink"
|
|
4
4
|
v-if="config.appLink"
|
|
5
5
|
:to
|
|
6
|
+
:replace
|
|
6
7
|
>
|
|
7
8
|
<slot />
|
|
8
9
|
</component>
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
<RouterLink
|
|
19
20
|
v-else
|
|
20
21
|
:to
|
|
22
|
+
:replace
|
|
21
23
|
><slot /></RouterLink>
|
|
22
24
|
</template>
|
|
23
25
|
|
|
@@ -33,6 +35,7 @@ const { locale } = useTranslation()
|
|
|
33
35
|
|
|
34
36
|
const props = defineProps<{
|
|
35
37
|
to: string | RouteLocationRaw | null | undefined
|
|
38
|
+
replace?: boolean
|
|
36
39
|
}>()
|
|
37
40
|
|
|
38
41
|
const isExternal = computed(() => {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
/>
|
|
18
18
|
<span
|
|
19
19
|
class="copy-label"
|
|
20
|
-
:class="{ 'sr-only': hideLabel }"
|
|
20
|
+
:class="{ 'sr-only': hideLabel || iconOnly }"
|
|
21
21
|
>{{ copiedLabel }}</span>
|
|
22
22
|
</span>
|
|
23
23
|
<span
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
/>
|
|
33
33
|
<span
|
|
34
34
|
class="copy-link copy-label"
|
|
35
|
-
:class="{ 'sr-only': hideLabel }"
|
|
35
|
+
:class="{ 'sr-only': hideLabel || iconOnly }"
|
|
36
36
|
>{{ label }}</span>
|
|
37
37
|
</span>
|
|
38
38
|
</button>
|
|
@@ -49,9 +49,13 @@ const props = withDefaults(defineProps<{
|
|
|
49
49
|
label: string
|
|
50
50
|
copiedLabel: string
|
|
51
51
|
hideLabel?: boolean
|
|
52
|
+
// Like hideLabel but keeps the default icon and has no bordered box — just the
|
|
53
|
+
// plain copy icon with an sr-only label.
|
|
54
|
+
iconOnly?: boolean
|
|
52
55
|
reverse?: boolean
|
|
53
56
|
}>(), {
|
|
54
57
|
hideLabel: false,
|
|
58
|
+
iconOnly: false,
|
|
55
59
|
reverse: false,
|
|
56
60
|
})
|
|
57
61
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
/>
|
|
11
11
|
</div>
|
|
12
12
|
<PreviewUnavailable v-else>
|
|
13
|
-
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le
|
|
13
|
+
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
|
|
14
14
|
</PreviewUnavailable>
|
|
15
15
|
</div>
|
|
16
16
|
</template>
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<div class="text-xs">
|
|
3
3
|
<PreviewUnavailable v-if="!isSizeAllowed">
|
|
4
4
|
{{ fileSizeBytes
|
|
5
|
-
? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le
|
|
6
|
-
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le
|
|
5
|
+
? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le pour le consulter.", { fileType })
|
|
6
|
+
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le pour le consulter.")
|
|
7
7
|
}}
|
|
8
8
|
</PreviewUnavailable>
|
|
9
9
|
<PreviewUnavailable v-else-if="hasError">
|
|
10
|
-
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le
|
|
10
|
+
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
|
|
11
11
|
</PreviewUnavailable>
|
|
12
12
|
<img
|
|
13
13
|
v-else
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
</PreviewUnavailable>
|
|
5
5
|
<div
|
|
6
6
|
v-else
|
|
7
|
-
id="map"
|
|
8
7
|
ref="mapRef"
|
|
8
|
+
class="size-full"
|
|
9
9
|
/>
|
|
10
10
|
</template>
|
|
11
11
|
|
|
@@ -128,10 +128,3 @@ onMounted(() => {
|
|
|
128
128
|
displayMap()
|
|
129
129
|
})
|
|
130
130
|
</script>
|
|
131
|
-
|
|
132
|
-
<style>
|
|
133
|
-
#map {
|
|
134
|
-
width: 100%;
|
|
135
|
-
height: 500px;
|
|
136
|
-
}
|
|
137
|
-
</style>
|
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<PreviewWrapper
|
|
3
|
-
v-slot="{ data }"
|
|
4
3
|
file-type="PDF"
|
|
5
4
|
:resource="resource"
|
|
6
5
|
:max-size="config.maxPdfPreviewByteSize"
|
|
7
6
|
:load="load"
|
|
7
|
+
:fill="fill"
|
|
8
8
|
@loaded="renderAllPages"
|
|
9
9
|
>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
<!-- While pdfjs downloads/parses the document, show the A4 skeleton instead of a
|
|
11
|
+
bare "loading" line — it matches the reader framing below seamlessly. -->
|
|
12
|
+
<template #loading>
|
|
13
|
+
<PdfSkeleton :fill="fill" />
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<!-- Cap the page width (A4 never needs more) and center on a light backdrop so
|
|
17
|
+
it reads like a document reader. The width cap applies in both modes since
|
|
18
|
+
the render width follows the container; `fill` only drives the height
|
|
19
|
+
(fullscreen fills down, inline stays capped). The scroll container is a plain
|
|
20
|
+
block (not a flex) so the trailing `pb` is reliably scrollable — flex + overflow
|
|
21
|
+
drops the end padding of flex items. -->
|
|
22
|
+
<template #default="{ data }">
|
|
23
|
+
<div
|
|
24
|
+
class="overflow-y-auto bg-gray-100"
|
|
25
|
+
:class="fill ? 'min-h-0 flex-1' : 'max-h-[80vh]'"
|
|
26
|
+
>
|
|
27
|
+
<div class="flex justify-center p-4 pb-16">
|
|
28
|
+
<div
|
|
29
|
+
ref="containerRef"
|
|
30
|
+
class="w-[800px] max-w-full space-y-3"
|
|
31
|
+
>
|
|
32
|
+
<canvas
|
|
33
|
+
v-for="page in (data as PDFDocumentProxy).numPages"
|
|
34
|
+
:key="page"
|
|
35
|
+
:ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
|
|
36
|
+
class="w-full bg-white shadow-sm"
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
21
42
|
</PreviewWrapper>
|
|
22
43
|
</template>
|
|
23
44
|
|
|
@@ -27,6 +48,7 @@ import * as pdfjsLib from 'pdfjs-dist'
|
|
|
27
48
|
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
|
|
28
49
|
import type { PDFDocumentProxy } from 'pdfjs-dist'
|
|
29
50
|
import PreviewWrapper from './PreviewWrapper.vue'
|
|
51
|
+
import PdfSkeleton from './PdfSkeleton.vue'
|
|
30
52
|
import { useComponentsConfig } from '../../config'
|
|
31
53
|
import type { Resource } from '../../types/resources'
|
|
32
54
|
|
|
@@ -34,6 +56,8 @@ pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
|
|
|
34
56
|
|
|
35
57
|
const props = defineProps<{
|
|
36
58
|
resource: Resource
|
|
59
|
+
// Fill the available height (fullscreen) instead of the default capped height.
|
|
60
|
+
fill?: boolean
|
|
37
61
|
}>()
|
|
38
62
|
|
|
39
63
|
const config = useComponentsConfig()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- An A4 sheet on the reader backdrop with a few pulsing text lines, matching the
|
|
3
|
+
real PdfPreview framing (centered, capped ~800px, gray backdrop). -->
|
|
4
|
+
<div
|
|
5
|
+
class="overflow-hidden bg-gray-100"
|
|
6
|
+
:class="fill ? 'min-h-0 flex-1' : 'max-h-[80vh]'"
|
|
7
|
+
role="status"
|
|
8
|
+
:aria-label="t('Chargement de l\'aperçu…')"
|
|
9
|
+
>
|
|
10
|
+
<div class="flex justify-center p-4">
|
|
11
|
+
<div class="aspect-[210/297] w-[800px] max-w-full bg-white p-10 shadow-sm">
|
|
12
|
+
<div class="animate-pulse-placeholder space-y-6">
|
|
13
|
+
<div class="space-y-3">
|
|
14
|
+
<div class="h-5 w-1/2 rounded bg-gray-200" />
|
|
15
|
+
<div class="h-3 w-full rounded bg-gray-200" />
|
|
16
|
+
<div class="h-3 w-full rounded bg-gray-200" />
|
|
17
|
+
<div class="h-3 w-5/6 rounded bg-gray-200" />
|
|
18
|
+
</div>
|
|
19
|
+
<div class="space-y-3">
|
|
20
|
+
<div class="h-3 w-full rounded bg-gray-200" />
|
|
21
|
+
<div class="h-3 w-11/12 rounded bg-gray-200" />
|
|
22
|
+
<div class="h-3 w-full rounded bg-gray-200" />
|
|
23
|
+
<div class="h-3 w-3/4 rounded bg-gray-200" />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="space-y-3">
|
|
26
|
+
<div class="h-3 w-full rounded bg-gray-200" />
|
|
27
|
+
<div class="h-3 w-5/6 rounded bg-gray-200" />
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup lang="ts">
|
|
36
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
37
|
+
|
|
38
|
+
defineProps<{
|
|
39
|
+
// Fill the available height (fullscreen) instead of the default capped height.
|
|
40
|
+
fill?: boolean
|
|
41
|
+
}>()
|
|
42
|
+
|
|
43
|
+
const { t } = useTranslation()
|
|
44
|
+
</script>
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="flex h-full flex-col">
|
|
3
3
|
<PreviewUnavailable v-if="hasError">
|
|
4
4
|
{{ t("L'aperçu cartographique de ce fichier n'a pas pu être chargé.") }}
|
|
5
5
|
</PreviewUnavailable>
|
|
6
6
|
<div
|
|
7
7
|
v-else
|
|
8
|
-
class="-
|
|
8
|
+
class="flex min-h-0 flex-1 flex-col"
|
|
9
9
|
>
|
|
10
10
|
<div
|
|
11
11
|
v-if="pmtilesViewerUrl"
|
|
12
|
-
class="bg-blue-100 text-datagouv fr-hidden fr-unhidden-md p-4"
|
|
12
|
+
class="bg-blue-100 text-datagouv fr-hidden fr-unhidden-md p-4 shrink-0"
|
|
13
13
|
>
|
|
14
14
|
<div class="fr-grid-row fr-grid-row--middle fr-grid-row--gutters">
|
|
15
15
|
<div
|
|
@@ -35,12 +35,17 @@
|
|
|
35
35
|
</p>
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
|
38
|
-
<div
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
<div class="relative min-h-0 flex-1">
|
|
39
|
+
<div
|
|
40
|
+
ref="containerRef"
|
|
41
|
+
class="size-full"
|
|
42
|
+
/>
|
|
43
|
+
<!-- Freshness caption overlaid inside the map (bottom-left, the corner left
|
|
44
|
+
free by the navigation/geolocate controls top-right and the attribution
|
|
45
|
+
bottom-right) so the map itself can use the whole height. -->
|
|
46
|
+
<div class="pointer-events-none absolute bottom-2 left-2 z-10 rounded bg-white/90 px-2 py-1 text-[12px] leading-4 text-gray-medium shadow-sm">
|
|
47
|
+
{{ t("Aperçu de la carte mis à jour le {date}", { date: lastUpdate }) }}
|
|
48
|
+
</div>
|
|
44
49
|
</div>
|
|
45
50
|
</div>
|
|
46
51
|
</div>
|
|
@@ -104,14 +109,22 @@ async function displayMap() {
|
|
|
104
109
|
const p = new PMTiles(pmtilesUrl.value)
|
|
105
110
|
protocol.add(p)
|
|
106
111
|
|
|
107
|
-
p.getHeader().then((h: {
|
|
112
|
+
p.getHeader().then((h: { minLon: number, minLat: number, maxLon: number, maxLat: number, centerZoom: number, centerLon: number, centerLat: number }) => {
|
|
113
|
+
// Frame the data's bounding box (carried by the archive header) so we land on
|
|
114
|
+
// the data. Fall back to the header center when the bbox is degenerate — an
|
|
115
|
+
// archive with a single feature, or bounds left unset (all zeroes).
|
|
116
|
+
const hasBounds = h.minLon < h.maxLon && h.minLat < h.maxLat
|
|
108
117
|
const map = new maplibregl.Map({
|
|
109
118
|
container: container.value!,
|
|
110
119
|
// JSON import is inferred too loosely (e.g. center as number[]) to match
|
|
111
120
|
// maplibre's strict StyleSpecification, so cast through unknown.
|
|
112
121
|
style: styleVector as unknown as maplibregl.StyleSpecification,
|
|
113
|
-
|
|
114
|
-
|
|
122
|
+
...(hasBounds
|
|
123
|
+
? {
|
|
124
|
+
bounds: [[h.minLon, h.minLat], [h.maxLon, h.maxLat]] as [[number, number], [number, number]],
|
|
125
|
+
fitBoundsOptions: { padding: 24 },
|
|
126
|
+
}
|
|
127
|
+
: { center: [h.centerLon, h.centerLat] as [number, number], zoom: h.centerZoom }),
|
|
115
128
|
})
|
|
116
129
|
map.addControl(new maplibregl.NavigationControl())
|
|
117
130
|
map.addControl(
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="text-xs"
|
|
4
|
+
:class="fill ? 'flex min-h-0 flex-1 flex-col' : ''"
|
|
5
|
+
>
|
|
3
6
|
<slot
|
|
4
7
|
v-if="data !== null"
|
|
5
8
|
:data="data"
|
|
6
9
|
/>
|
|
7
|
-
<
|
|
10
|
+
<slot
|
|
8
11
|
v-else-if="loading"
|
|
9
|
-
|
|
12
|
+
name="loading"
|
|
10
13
|
>
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
<div class="text-gray-medium">
|
|
15
|
+
{{ t("Chargement de l'aperçu {fileType}...", { fileType }) }}
|
|
16
|
+
</div>
|
|
17
|
+
</slot>
|
|
13
18
|
<PreviewUnavailable v-else-if="!isSizeAllowed">
|
|
14
19
|
{{ fileSizeBytes
|
|
15
|
-
? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le
|
|
16
|
-
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le
|
|
20
|
+
? t("Le fichier {fileType} est trop volumineux pour être prévisualisé. Téléchargez-le pour le consulter.", { fileType })
|
|
21
|
+
: t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le pour le consulter.")
|
|
17
22
|
}}
|
|
18
23
|
</PreviewUnavailable>
|
|
19
24
|
<PreviewUnavailable v-else-if="corsStatus === 'blocked'">
|
|
20
|
-
{{ t("Ce fichier {fileType} ne peut pas être prévisualisé car il est hébergé sur un site distant qui restreint l'accès (CORS). Téléchargez-le
|
|
25
|
+
{{ t("Ce fichier {fileType} ne peut pas être prévisualisé car il est hébergé sur un site distant qui restreint l'accès (CORS). Téléchargez-le pour le consulter.", { fileType }) }}
|
|
21
26
|
</PreviewUnavailable>
|
|
22
27
|
<PreviewUnavailable v-else-if="error === 'network'">
|
|
23
|
-
{{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le
|
|
28
|
+
{{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le pour le consulter.") }}
|
|
24
29
|
</PreviewUnavailable>
|
|
25
30
|
<PreviewUnavailable v-else-if="error">
|
|
26
|
-
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le
|
|
31
|
+
{{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le pour le consulter.") }}
|
|
27
32
|
</PreviewUnavailable>
|
|
28
33
|
</div>
|
|
29
34
|
</template>
|
|
@@ -40,6 +45,8 @@ const props = defineProps<{
|
|
|
40
45
|
resource: Resource
|
|
41
46
|
maxSize: number | undefined
|
|
42
47
|
load: () => Promise<unknown>
|
|
48
|
+
// Fill the available height (fullscreen) instead of sizing to content.
|
|
49
|
+
fill?: boolean
|
|
43
50
|
}>()
|
|
44
51
|
|
|
45
52
|
const emit = defineEmits<{
|