@datagouv/components-next 1.0.0 → 1.0.1
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/assets/main.css +0 -28
- package/dist/{Datafair.client-Dls5AHTE.js → Datafair.client-B5lBpOl8.js} +2 -2
- package/dist/{JsonPreview.client-DPDTs433.js → JsonPreview.client-Doz1Z0BS.js} +16 -16
- package/dist/{MapContainer.client-BdAzd7bj.js → MapContainer.client-oiieO8H-.js} +3 -3
- package/dist/PdfPreview.client-CdAhkDFJ.js +14513 -0
- package/dist/{Pmtiles.client-mF6xaOO_.js → Pmtiles.client-B0v8tGJQ.js} +2 -2
- package/dist/Swagger.client-CsK65JnG.js +4 -0
- package/dist/{XmlPreview.client-C0OgBkSq.js → XmlPreview.client-CrjHf74q.js} +15 -15
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +130 -125
- package/dist/components.css +1 -1
- package/dist/{index-BRGqW8aQ.js → index-Bbu9rOHt.js} +1 -1
- package/dist/{main-CNHxAJ8J.js → main-CiH8ZmBI.js} +22114 -21911
- package/dist/{vue3-xml-viewer.common-CmAdQfIy.js → vue3-xml-viewer.common-Bi_bsV6C.js} +1 -1
- package/package.json +2 -2
- package/src/components/DataserviceCard.vue +3 -3
- package/src/components/DatasetCard.vue +2 -2
- package/src/components/DatasetQuality.vue +23 -16
- package/src/components/DatasetQualityInline.vue +13 -17
- package/src/components/DatasetQualityScore.vue +12 -15
- package/src/components/DiscussionMessageCard.vue +1 -1
- package/src/components/ObjectCard.vue +2 -2
- package/src/components/ObjectCardHeader.vue +1 -1
- package/src/components/OrganizationHorizontalCard.vue +87 -0
- package/src/components/OrganizationNameWithCertificate.vue +1 -1
- package/src/components/ProgressBar.vue +31 -0
- package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
- package/src/components/ResourceAccordion/JsonPreview.client.vue +3 -3
- package/src/components/ResourceAccordion/MapContainer.client.vue +1 -1
- package/src/components/ResourceAccordion/PdfPreview.client.vue +70 -74
- package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
- package/src/components/ResourceAccordion/Preview.vue +1 -1
- package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -8
- package/src/components/ResourceAccordion/XmlPreview.client.vue +3 -3
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +50 -1
- package/src/components/ReuseHorizontalCard.vue +1 -1
- package/src/components/Search/Filter/ProducerTypeFilter.vue +13 -3
- package/src/components/Search/GlobalSearch.vue +124 -28
- package/src/components/Toggletip.vue +5 -2
- package/src/components/TopicCard.vue +1 -1
- package/src/composables/useHasTabularData.ts +15 -0
- package/src/composables/useResourceCapabilities.ts +18 -5
- package/src/composables/useTranslation.ts +2 -1
- package/src/functions/api.ts +11 -3
- package/src/functions/api.types.ts +1 -1
- package/src/functions/resourceCapabilities.ts +55 -0
- package/src/main.ts +8 -1
- package/src/types/resources.ts +10 -0
- package/src/types/search.ts +29 -1
- package/dist/PdfPreview.client-CopqSDyt.js +0 -107
- package/dist/Swagger.client-eJ7gpfZA.js +0 -4
- package/dist/pdf-vue3-IkJO65RH.js +0 -273
- package/dist/pdf.min-f72cfa08-CdgJTooZ.js +0 -9501
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datagouv/components-next",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"maplibre-gl": "^5.6.2",
|
|
31
31
|
"ofetch": "^1.4.1",
|
|
32
32
|
"ol": "^10.6.1",
|
|
33
|
-
"
|
|
33
|
+
"pdfjs-dist": "^4.10.38",
|
|
34
34
|
"pmtiles": "^4.3.0",
|
|
35
35
|
"popmotion": "^11.0",
|
|
36
36
|
"rehype-highlight": "^7.0.2",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
</ObjectCardHeader>
|
|
59
59
|
<div
|
|
60
60
|
v-if="dataservice.organization || dataservice.owner"
|
|
61
|
-
class="text-sm
|
|
61
|
+
class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
|
|
62
62
|
>
|
|
63
63
|
<ObjectCardOwner
|
|
64
64
|
:organization="dataservice.organization"
|
|
@@ -70,14 +70,14 @@
|
|
|
70
70
|
class="size-4 flex-none fill-gray-medium"
|
|
71
71
|
/>
|
|
72
72
|
<!-- https://github.com/datagouv/cdata/issues/653 -->
|
|
73
|
-
<p class="text-sm whitespace-nowrap mb-0">
|
|
73
|
+
<p class="text-sm whitespace-nowrap mb-0 text-gray-medium">
|
|
74
74
|
{{ t('Mis à jour {date}', { date: formatRelativeIfRecentDate(dataservice.metadata_modified_at, { dateStyle: 'medium' }) }) }}
|
|
75
75
|
</p>
|
|
76
76
|
<RiSubtractLine
|
|
77
77
|
aria-hidden="true"
|
|
78
78
|
class="size-4 flex-none fill-gray-medium"
|
|
79
79
|
/>
|
|
80
|
-
<div class="flex items-center gap-1">
|
|
80
|
+
<div class="flex items-center gap-1 text-gray-medium">
|
|
81
81
|
<p
|
|
82
82
|
class="text-sm mb-0 flex items-center gap-0.5"
|
|
83
83
|
:aria-label="t('{n} vues | {n} vue | {n} vues', dataservice.metrics.views)"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
</ObjectCardHeader>
|
|
48
48
|
<div
|
|
49
49
|
v-if="dataset.organization || dataset.owner"
|
|
50
|
-
class="text-sm
|
|
50
|
+
class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
|
|
51
51
|
>
|
|
52
52
|
<ObjectCardOwner
|
|
53
53
|
:organization="dataset.organization"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
</div>
|
|
61
61
|
</div>
|
|
62
62
|
<div class="mx-0 -mb-1 flex flex-wrap items-center text-sm text-gray-medium">
|
|
63
|
-
<div class="hidden sm:flex text-gray-medium
|
|
63
|
+
<div class="hidden sm:flex text-gray-medium">
|
|
64
64
|
<DatasetQualityInline :quality="dataset.quality" />
|
|
65
65
|
</div>
|
|
66
66
|
<RiSubtractLine
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
<Toggletip
|
|
3
|
+
:styled-button="false"
|
|
4
|
+
button-class="flex flex-col 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
|
+
<span class="flex items-center gap-1">
|
|
8
|
+
<RiInformationLine
|
|
9
|
+
class="size-5 shrink-0"
|
|
10
|
+
aria-hidden="true"
|
|
11
|
+
/>
|
|
12
|
+
<span class="text-sm text-gray-plain font-bold">
|
|
13
|
+
{{ t('Qualité des métadonnées:') }}
|
|
14
|
+
</span>
|
|
15
|
+
</span>
|
|
16
|
+
<DatasetQualityScore
|
|
17
|
+
:score="quality.score"
|
|
18
|
+
class="w-full"
|
|
19
|
+
/>
|
|
20
|
+
<template #toggletip>
|
|
21
|
+
<DatasetQualityTooltipContent :quality />
|
|
22
|
+
</template>
|
|
23
|
+
</Toggletip>
|
|
18
24
|
<template v-if="!hideWarnings">
|
|
19
25
|
<ul class="list-none pl-0">
|
|
20
26
|
<DatasetQualityItemWarning
|
|
@@ -54,6 +60,7 @@
|
|
|
54
60
|
</template>
|
|
55
61
|
|
|
56
62
|
<script setup lang="ts">
|
|
63
|
+
import { RiInformationLine } from '@remixicon/vue'
|
|
57
64
|
import type { Quality } from '../types/datasets'
|
|
58
65
|
import DatasetQualityItemWarning from './DatasetQualityItemWarning.vue'
|
|
59
66
|
import DatasetQualityScore from './DatasetQualityScore.vue'
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="m-0
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<div class="fr-grid-row fr-grid-row--middle fr-mr-1v">
|
|
16
|
-
<DatasetQualityScore :score="quality.score" />
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
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
|
+
<DatasetQualityScore :score="quality.score" />
|
|
11
|
+
<template #toggletip>
|
|
12
|
+
<DatasetQualityTooltipContent :quality />
|
|
13
|
+
</template>
|
|
14
|
+
</Toggletip>
|
|
19
15
|
</div>
|
|
20
16
|
</template>
|
|
21
17
|
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
class="quality-score"
|
|
4
|
-
:class="props.class"
|
|
5
|
-
min="0"
|
|
6
|
-
low="0"
|
|
7
|
-
:high="high"
|
|
8
|
-
:max="quality_max_score"
|
|
9
|
-
:optimum="quality_max_score"
|
|
2
|
+
<ProgressBar
|
|
10
3
|
:value="score"
|
|
4
|
+
:max="quality_max_score"
|
|
5
|
+
:aria-label="score >= high ? t('Bon') : t('À améliorer')"
|
|
6
|
+
:bar-class="score >= high ? 'bg-success-dark' : 'bg-gray-low'"
|
|
7
|
+
:class="props.class"
|
|
11
8
|
>
|
|
12
|
-
<
|
|
13
|
-
{{ t('Bon') }}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
</meter>
|
|
9
|
+
<span class="sr-only">
|
|
10
|
+
<template v-if="score >= high">{{ t('Bon') }}</template>
|
|
11
|
+
<template v-else>{{ t('À améliorer') }}</template>
|
|
12
|
+
({{ calculatedScore }})
|
|
13
|
+
</span>
|
|
14
|
+
</ProgressBar>
|
|
19
15
|
</template>
|
|
20
16
|
|
|
21
17
|
<script setup lang="ts">
|
|
22
18
|
import { computed } from 'vue'
|
|
23
19
|
import { useTranslation } from '../composables/useTranslation'
|
|
20
|
+
import ProgressBar from './ProgressBar.vue'
|
|
24
21
|
|
|
25
22
|
const props = withDefaults(defineProps<{
|
|
26
23
|
score: number
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
<div
|
|
11
11
|
v-if="discussion.organization || discussion.user"
|
|
12
|
-
class="text-sm
|
|
12
|
+
class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
|
|
13
13
|
>
|
|
14
14
|
<ObjectCardOwner
|
|
15
15
|
:organization="discussion.organization"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<slot name="media" />
|
|
17
17
|
</div>
|
|
18
18
|
</div>
|
|
19
|
-
<div class="flex-1 overflow-hidden">
|
|
19
|
+
<div class="flex-1 overflow-hidden space-y-1">
|
|
20
20
|
<slot />
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
@@ -35,7 +35,7 @@ const props = withDefaults(defineProps<{
|
|
|
35
35
|
|
|
36
36
|
const mediaContainerClass = computed(() => {
|
|
37
37
|
if (props.mediaSize === 'lg') {
|
|
38
|
-
return 'w-[
|
|
38
|
+
return 'w-[225px] h-[120px]'
|
|
39
39
|
}
|
|
40
40
|
return 'p-2'
|
|
41
41
|
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ObjectCard>
|
|
3
|
+
<template #media>
|
|
4
|
+
<OrganizationLogo
|
|
5
|
+
:organization="organization"
|
|
6
|
+
size-class="size-12"
|
|
7
|
+
/>
|
|
8
|
+
</template>
|
|
9
|
+
<ObjectCardHeader
|
|
10
|
+
:icon="RiBuilding2Line"
|
|
11
|
+
:url="organization.page"
|
|
12
|
+
>
|
|
13
|
+
<OrganizationNameWithCertificate
|
|
14
|
+
:show-type="false"
|
|
15
|
+
:organization
|
|
16
|
+
size="sm"
|
|
17
|
+
color-class="text-gray-title"
|
|
18
|
+
/>
|
|
19
|
+
</ObjectCardHeader>
|
|
20
|
+
<div class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate">
|
|
21
|
+
<template v-if="type !== 'other'">
|
|
22
|
+
<OwnerType
|
|
23
|
+
class="mb-0"
|
|
24
|
+
:type
|
|
25
|
+
/>
|
|
26
|
+
<RiSubtractLine
|
|
27
|
+
v-if="'metrics' in organization"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
class="hidden md:block size-4 flex-none fill-gray-medium"
|
|
30
|
+
/>
|
|
31
|
+
</template>
|
|
32
|
+
<div
|
|
33
|
+
v-if="'metrics' in organization"
|
|
34
|
+
class="text-gray-medium flex items-center text-sm gap-0.5"
|
|
35
|
+
:aria-label="t('{datasets} jeux de donn\u00e9es, {dataservices} API et {reuses} r\u00e9utilisations', {
|
|
36
|
+
datasets: organization.metrics.datasets,
|
|
37
|
+
dataservices: organization.metrics.dataservices,
|
|
38
|
+
reuses: organization.metrics.reuses,
|
|
39
|
+
})"
|
|
40
|
+
>
|
|
41
|
+
<RiDatabase2Line
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
class="size-3.5"
|
|
44
|
+
/> {{ summarize(organization.metrics.datasets) }}
|
|
45
|
+
<RiTerminalLine
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
class="size-3.5 ml-1"
|
|
48
|
+
/> {{ summarize(organization.metrics.dataservices) }}
|
|
49
|
+
<RiLineChartLine
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
class="size-3.5 ml-1"
|
|
52
|
+
/> {{ summarize(organization.metrics.reuses) }}
|
|
53
|
+
<RiStarLine
|
|
54
|
+
aria-hidden="true"
|
|
55
|
+
class="size-3.5 ml-1"
|
|
56
|
+
/> {{ summarize(organization.metrics.followers) }}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<ObjectCardShortDescription
|
|
60
|
+
v-if="'description' in organization"
|
|
61
|
+
:text="organization.description"
|
|
62
|
+
/>
|
|
63
|
+
</ObjectCard>
|
|
64
|
+
</template>
|
|
65
|
+
|
|
66
|
+
<script setup lang="ts">
|
|
67
|
+
import { RiBuilding2Line, RiDatabase2Line, RiLineChartLine, RiStarLine, RiSubtractLine, RiTerminalLine } from '@remixicon/vue'
|
|
68
|
+
import { computed } from 'vue'
|
|
69
|
+
import { getOrganizationType } from '../functions/organizations'
|
|
70
|
+
import { summarize } from '../functions/helpers'
|
|
71
|
+
import { useTranslation } from '../composables/useTranslation'
|
|
72
|
+
import type { Organization, OrganizationReference } from '../types/organizations'
|
|
73
|
+
import OrganizationLogo from './OrganizationLogo.vue'
|
|
74
|
+
import OrganizationNameWithCertificate from './OrganizationNameWithCertificate.vue'
|
|
75
|
+
import OwnerType from './OwnerType.vue'
|
|
76
|
+
import ObjectCard from './ObjectCard.vue'
|
|
77
|
+
import ObjectCardHeader from './ObjectCardHeader.vue'
|
|
78
|
+
import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
|
|
79
|
+
|
|
80
|
+
const props = defineProps<{
|
|
81
|
+
organization: Organization | OrganizationReference
|
|
82
|
+
}>()
|
|
83
|
+
|
|
84
|
+
const { t } = useTranslation()
|
|
85
|
+
|
|
86
|
+
const type = computed(() => getOrganizationType(props.organization))
|
|
87
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
role="meter"
|
|
4
|
+
:aria-valuenow="value"
|
|
5
|
+
aria-valuemin="0"
|
|
6
|
+
:aria-valuemax="max"
|
|
7
|
+
class="min-w-20 h-2.5 rounded-lg border border-gray-default bg-[#f5f5f5] overflow-hidden"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
class="h-full rounded-lg"
|
|
11
|
+
:class="barClass"
|
|
12
|
+
:style="{ width: percentage + '%' }"
|
|
13
|
+
/>
|
|
14
|
+
<slot />
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { computed } from 'vue'
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<{
|
|
22
|
+
value: number
|
|
23
|
+
max?: number
|
|
24
|
+
barClass?: string
|
|
25
|
+
}>(), {
|
|
26
|
+
max: 1,
|
|
27
|
+
barClass: '',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const percentage = computed(() => Math.min(100, Math.max(0, (props.value / props.max) * 100)))
|
|
31
|
+
</script>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
type="warning"
|
|
23
23
|
class="flex items-center space-x-2"
|
|
24
24
|
>
|
|
25
|
-
<RiErrorWarningLine class="
|
|
25
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
26
26
|
<span>{{ fileSizeBytes
|
|
27
27
|
? t("Fichier JSON trop volumineux pour l'aperçu. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
|
|
28
28
|
: t("L'aperçu n'est pas disponible car la taille du fichier est inconnue. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
type="warning"
|
|
34
34
|
class="flex items-center space-x-2"
|
|
35
35
|
>
|
|
36
|
-
<RiErrorWarningLine class="
|
|
36
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
37
37
|
<span>{{ t("Ce fichier JSON ne peut pas être prévisualisé, peut-être parce qu'il est hébergé sur un autre site qui ne l'autorise pas. Pour le consulter, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.") }}</span>
|
|
38
38
|
</SimpleBanner>
|
|
39
39
|
<SimpleBanner
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
type="warning"
|
|
42
42
|
class="flex items-center space-x-2"
|
|
43
43
|
>
|
|
44
|
-
<RiErrorWarningLine class="
|
|
44
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
45
45
|
<span>{{ t("Erreur lors du chargement de l'aperçu JSON.") }}</span>
|
|
46
46
|
</SimpleBanner>
|
|
47
47
|
</div>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
type="warning"
|
|
5
5
|
class="flex items-center space-x-2"
|
|
6
6
|
>
|
|
7
|
-
<RiErrorWarningLine class="
|
|
7
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
8
8
|
<span>{{ t("L'aperçu cartographique de ce fichier n'a pas pu être chargé.") }}</span>
|
|
9
9
|
</SimpleBanner>
|
|
10
10
|
<div
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="text-xs">
|
|
3
|
-
<div
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
:
|
|
11
|
-
:
|
|
12
|
-
progress-color="#0063cb"
|
|
13
|
-
:show-page-tooltip="true"
|
|
14
|
-
:show-back-to-top-btn="true"
|
|
15
|
-
:scroll-threshold="300"
|
|
16
|
-
pdf-width="100%"
|
|
17
|
-
:row-gap="12"
|
|
18
|
-
:use-system-fonts="true"
|
|
19
|
-
:disable-range="false"
|
|
20
|
-
:disable-stream="false"
|
|
21
|
-
:disable-auto-fetch="false"
|
|
3
|
+
<div
|
|
4
|
+
v-if="pdfReady"
|
|
5
|
+
ref="containerRef"
|
|
6
|
+
class="w-full overflow-y-auto max-h-[80vh] space-y-3"
|
|
7
|
+
>
|
|
8
|
+
<canvas
|
|
9
|
+
v-for="page in totalPages"
|
|
10
|
+
:key="page"
|
|
11
|
+
:ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
|
|
22
12
|
class="w-full"
|
|
23
|
-
@on-progress="handleProgress"
|
|
24
|
-
@on-complete="handleComplete"
|
|
25
|
-
@on-page-change="handlePageChange"
|
|
26
|
-
@on-pdf-init="handlePdfInit"
|
|
27
|
-
@on-error="handleError"
|
|
28
13
|
/>
|
|
29
14
|
</div>
|
|
30
15
|
<div
|
|
@@ -64,19 +49,18 @@
|
|
|
64
49
|
</template>
|
|
65
50
|
|
|
66
51
|
<script setup lang="ts">
|
|
67
|
-
import { computed,
|
|
52
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
68
53
|
import { RiErrorWarningLine } from '@remixicon/vue'
|
|
54
|
+
import * as pdfjsLib from 'pdfjs-dist'
|
|
55
|
+
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
|
|
56
|
+
import type { PDFDocumentProxy } from 'pdfjs-dist'
|
|
69
57
|
import SimpleBanner from '../SimpleBanner.vue'
|
|
70
58
|
import { useComponentsConfig } from '../../config'
|
|
71
59
|
import type { Resource } from '../../types/resources'
|
|
72
60
|
import { useTranslation } from '../../composables/useTranslation'
|
|
73
61
|
import { getResourceFilesize } from '../../functions/datasets'
|
|
74
62
|
|
|
75
|
-
|
|
76
|
-
import('pdf-vue3').then((module) => {
|
|
77
|
-
return module.default
|
|
78
|
-
}),
|
|
79
|
-
)
|
|
63
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
|
|
80
64
|
|
|
81
65
|
const props = defineProps<{
|
|
82
66
|
resource: Resource
|
|
@@ -85,10 +69,47 @@ const props = defineProps<{
|
|
|
85
69
|
const config = useComponentsConfig()
|
|
86
70
|
const { t } = useTranslation()
|
|
87
71
|
|
|
88
|
-
const
|
|
72
|
+
const containerRef = ref<HTMLElement | null>(null)
|
|
73
|
+
const pdfReady = ref(false)
|
|
89
74
|
const loading = ref(false)
|
|
90
75
|
const error = ref<string | null>(null)
|
|
91
76
|
const fileTooLarge = ref(false)
|
|
77
|
+
const totalPages = ref(0)
|
|
78
|
+
|
|
79
|
+
let pdfDoc: PDFDocumentProxy | null = null
|
|
80
|
+
const canvasRefs = new Map<number, HTMLCanvasElement>()
|
|
81
|
+
|
|
82
|
+
function setCanvasRef(el: HTMLCanvasElement | null, pageNum: number) {
|
|
83
|
+
if (el) canvasRefs.set(pageNum, el)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function renderPage(pageNum: number) {
|
|
87
|
+
if (!pdfDoc) return
|
|
88
|
+
|
|
89
|
+
const canvas = canvasRefs.get(pageNum)
|
|
90
|
+
if (!canvas) return
|
|
91
|
+
|
|
92
|
+
const page = await pdfDoc.getPage(pageNum)
|
|
93
|
+
|
|
94
|
+
const containerWidth = containerRef.value?.clientWidth ?? 800
|
|
95
|
+
const unscaledViewport = page.getViewport({ scale: 1 })
|
|
96
|
+
const scale = containerWidth / unscaledViewport.width
|
|
97
|
+
const viewport = page.getViewport({ scale })
|
|
98
|
+
|
|
99
|
+
const dpr = window.devicePixelRatio || 1
|
|
100
|
+
canvas.width = viewport.width * dpr
|
|
101
|
+
canvas.height = viewport.height * dpr
|
|
102
|
+
canvas.style.width = `${viewport.width}px`
|
|
103
|
+
canvas.style.height = `${viewport.height}px`
|
|
104
|
+
|
|
105
|
+
const context = canvas.getContext('2d')!
|
|
106
|
+
context.scale(dpr, dpr)
|
|
107
|
+
|
|
108
|
+
await page.render({
|
|
109
|
+
canvasContext: context,
|
|
110
|
+
viewport,
|
|
111
|
+
}).promise
|
|
112
|
+
}
|
|
92
113
|
|
|
93
114
|
const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
|
|
94
115
|
|
|
@@ -105,7 +126,6 @@ const shouldLoadPdf = computed(() => {
|
|
|
105
126
|
})
|
|
106
127
|
|
|
107
128
|
const loadPdf = async () => {
|
|
108
|
-
// Check if file is too large or size is unknown before loading
|
|
109
129
|
if (!shouldLoadPdf.value) {
|
|
110
130
|
fileTooLarge.value = true
|
|
111
131
|
return
|
|
@@ -115,19 +135,23 @@ const loadPdf = async () => {
|
|
|
115
135
|
error.value = null
|
|
116
136
|
|
|
117
137
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
const loadingTask = pdfjsLib.getDocument({
|
|
139
|
+
url: props.resource.url,
|
|
140
|
+
isEvalSupported: false,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
pdfDoc = await loadingTask.promise
|
|
144
|
+
totalPages.value = pdfDoc.numPages
|
|
145
|
+
pdfReady.value = true
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
await nextTick()
|
|
148
|
+
|
|
149
|
+
for (let i = 1; i <= pdfDoc.numPages; i++) {
|
|
150
|
+
await renderPage(i)
|
|
151
|
+
}
|
|
128
152
|
}
|
|
129
153
|
catch (err) {
|
|
130
|
-
console.error('Error
|
|
154
|
+
console.error('Error loading PDF:', err)
|
|
131
155
|
|
|
132
156
|
if (err instanceof TypeError) {
|
|
133
157
|
error.value = 'network'
|
|
@@ -135,45 +159,17 @@ const loadPdf = async () => {
|
|
|
135
159
|
else {
|
|
136
160
|
error.value = 'generic'
|
|
137
161
|
}
|
|
138
|
-
|
|
139
|
-
pdfData.value = false
|
|
140
162
|
}
|
|
141
163
|
finally {
|
|
142
164
|
loading.value = false
|
|
143
165
|
}
|
|
144
166
|
}
|
|
145
167
|
|
|
146
|
-
// Event handlers for PDF component
|
|
147
|
-
const handleProgress = (loadRatio: number) => {
|
|
148
|
-
console.log(`PDF loading progress: ${loadRatio}%`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const handleComplete = () => {
|
|
152
|
-
console.log('PDF download completed')
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const handlePageChange = (page: number) => {
|
|
156
|
-
console.log(`PDF page changed to: ${page}`)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const handlePdfInit = (pdf: unknown) => {
|
|
160
|
-
console.log('PDF initialized:', pdf)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const handleError = (err: unknown) => {
|
|
164
|
-
console.error('PDF loading error:', err)
|
|
165
|
-
|
|
166
|
-
if (err instanceof TypeError) {
|
|
167
|
-
error.value = 'network'
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
error.value = 'generic'
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
pdfData.value = false
|
|
174
|
-
}
|
|
175
|
-
|
|
176
168
|
onMounted(() => {
|
|
177
169
|
loadPdf()
|
|
178
170
|
})
|
|
171
|
+
|
|
172
|
+
onBeforeUnmount(() => {
|
|
173
|
+
pdfDoc?.destroy()
|
|
174
|
+
})
|
|
179
175
|
</script>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
type="warning"
|
|
6
6
|
class="flex items-center space-x-2"
|
|
7
7
|
>
|
|
8
|
-
<RiErrorWarningLine class="
|
|
8
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
9
9
|
<span>{{ t("L'aperçu cartographique de ce fichier n'a pas pu être chargé.") }}</span>
|
|
10
10
|
</SimpleBanner>
|
|
11
11
|
<div
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
type="warning"
|
|
6
6
|
class="flex items-center space-x-2"
|
|
7
7
|
>
|
|
8
|
-
<RiErrorWarningLine class="
|
|
8
|
+
<RiErrorWarningLine class="shrink-0 size-6" />
|
|
9
9
|
<span>{{ t("L'aperçu de ce fichier n'a pas pu être chargé.") }}</span>
|
|
10
10
|
</SimpleBanner>
|
|
11
11
|
<PreviewLoader v-else-if="loading" />
|