@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.
Files changed (53) hide show
  1. package/assets/main.css +0 -28
  2. package/dist/{Datafair.client-Dls5AHTE.js → Datafair.client-B5lBpOl8.js} +2 -2
  3. package/dist/{JsonPreview.client-DPDTs433.js → JsonPreview.client-Doz1Z0BS.js} +16 -16
  4. package/dist/{MapContainer.client-BdAzd7bj.js → MapContainer.client-oiieO8H-.js} +3 -3
  5. package/dist/PdfPreview.client-CdAhkDFJ.js +14513 -0
  6. package/dist/{Pmtiles.client-mF6xaOO_.js → Pmtiles.client-B0v8tGJQ.js} +2 -2
  7. package/dist/Swagger.client-CsK65JnG.js +4 -0
  8. package/dist/{XmlPreview.client-C0OgBkSq.js → XmlPreview.client-CrjHf74q.js} +15 -15
  9. package/dist/components-next.css +1 -1
  10. package/dist/components-next.js +130 -125
  11. package/dist/components.css +1 -1
  12. package/dist/{index-BRGqW8aQ.js → index-Bbu9rOHt.js} +1 -1
  13. package/dist/{main-CNHxAJ8J.js → main-CiH8ZmBI.js} +22114 -21911
  14. package/dist/{vue3-xml-viewer.common-CmAdQfIy.js → vue3-xml-viewer.common-Bi_bsV6C.js} +1 -1
  15. package/package.json +2 -2
  16. package/src/components/DataserviceCard.vue +3 -3
  17. package/src/components/DatasetCard.vue +2 -2
  18. package/src/components/DatasetQuality.vue +23 -16
  19. package/src/components/DatasetQualityInline.vue +13 -17
  20. package/src/components/DatasetQualityScore.vue +12 -15
  21. package/src/components/DiscussionMessageCard.vue +1 -1
  22. package/src/components/ObjectCard.vue +2 -2
  23. package/src/components/ObjectCardHeader.vue +1 -1
  24. package/src/components/OrganizationHorizontalCard.vue +87 -0
  25. package/src/components/OrganizationNameWithCertificate.vue +1 -1
  26. package/src/components/ProgressBar.vue +31 -0
  27. package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
  28. package/src/components/ResourceAccordion/JsonPreview.client.vue +3 -3
  29. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -1
  30. package/src/components/ResourceAccordion/PdfPreview.client.vue +70 -74
  31. package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
  32. package/src/components/ResourceAccordion/Preview.vue +1 -1
  33. package/src/components/ResourceAccordion/ResourceAccordion.vue +5 -8
  34. package/src/components/ResourceAccordion/XmlPreview.client.vue +3 -3
  35. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +50 -1
  36. package/src/components/ReuseHorizontalCard.vue +1 -1
  37. package/src/components/Search/Filter/ProducerTypeFilter.vue +13 -3
  38. package/src/components/Search/GlobalSearch.vue +124 -28
  39. package/src/components/Toggletip.vue +5 -2
  40. package/src/components/TopicCard.vue +1 -1
  41. package/src/composables/useHasTabularData.ts +15 -0
  42. package/src/composables/useResourceCapabilities.ts +18 -5
  43. package/src/composables/useTranslation.ts +2 -1
  44. package/src/functions/api.ts +11 -3
  45. package/src/functions/api.types.ts +1 -1
  46. package/src/functions/resourceCapabilities.ts +55 -0
  47. package/src/main.ts +8 -1
  48. package/src/types/resources.ts +10 -0
  49. package/src/types/search.ts +29 -1
  50. package/dist/PdfPreview.client-CopqSDyt.js +0 -107
  51. package/dist/Swagger.client-eJ7gpfZA.js +0 -4
  52. package/dist/pdf-vue3-IkJO65RH.js +0 -273
  53. package/dist/pdf.min-f72cfa08-CdgJTooZ.js +0 -9501
@@ -1,4 +1,4 @@
1
- import { b as Ke } from "./main-CNHxAJ8J.js";
1
+ import { b as Ke } from "./main-CiH8ZmBI.js";
2
2
  import We from "vue";
3
3
  function Fe(I, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.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
- "pdf-vue3": "^1.0.12",
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 m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
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 m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
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 -ml-2.5">
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
- <div class="flex items-center">
3
- <Toggletip
4
- :button-props="{ class: '-ml-2 mt-px', title: t('Qualité des métadonnées') }"
5
- >
6
- <template #toggletip>
7
- <DatasetQualityTooltipContent :quality />
8
- </template>
9
- </Toggletip>
10
- <div class="text-sm text-gray-plain font-bold">
11
- {{ t('Qualité des métadonnées:') }}
12
- </div>
13
- </div>
14
- <DatasetQualityScore
15
- :score="quality.score"
16
- class="w-full"
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 flex flex-wrap items-center text-sm text-gray-medium">
3
- <div class="fr-grid-row fr-grid-row--middle">
4
- <Toggletip
5
- :button-props="{ class: 'relative z-2 ml-0.5', title: t('Qualité des métadonnées') }"
6
- >
7
- <RiInformationLine class="size-4" />
8
- <template #toggletip>
9
- <DatasetQualityTooltipContent :quality />
10
- </template>
11
- </Toggletip>
12
- <p class="my-0 mr-1 text-gray-medium text-sm">
13
- {{ t('Métadonnées :') }}
14
- </p>
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
- <meter
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
- <template v-if="score >= high">
13
- {{ t('Bon') }}
14
- </template>
15
- <template v-else>
16
- {{ t('À améliorer') }}
17
- </template>({{ calculatedScore }})
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 m-0 flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate"
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-[240px] h-[160px]'
38
+ return 'w-[225px] h-[120px]'
39
39
  }
40
40
  return 'p-2'
41
41
  })
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <h3 class="w-full text-base mb-0 flex">
2
+ <h3 class="w-full text-base flex">
3
3
  <AppLink
4
4
  :to="url"
5
5
  class="text-gray-title text-base bg-none flex items-center w-full truncate gap-1"
@@ -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>
@@ -6,7 +6,7 @@
6
6
  />
7
7
  <component
8
8
  :is="as"
9
- class="mb-0 truncate flex-initial font-normal"
9
+ class="mb-0 truncate flex-initial"
10
10
  :class="[colorClass, { 'text-xs': size === 'xs', 'text-sm': size === 'sm', 'text-base': size === 'base' }]"
11
11
  >
12
12
  {{ organization.name }}
@@ -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>
@@ -14,7 +14,7 @@
14
14
  type="warning"
15
15
  class="flex items-center space-x-2"
16
16
  >
17
- <RiErrorWarningLine class="shink-0 size-6" />
17
+ <RiErrorWarningLine class="shrink-0 size-6" />
18
18
  <span>{{ t("Erreur lors de l'affichage de l'aperçu.") }}</span>
19
19
  </SimpleBanner>
20
20
  </div>
@@ -22,7 +22,7 @@
22
22
  type="warning"
23
23
  class="flex items-center space-x-2"
24
24
  >
25
- <RiErrorWarningLine class="shink-0 size-6" />
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="shink-0 size-6" />
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="shink-0 size-6" />
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="shink-0 size-6" />
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 v-if="pdfData">
4
- <!--
5
- We use props.resource.url instead of props.resource.latest
6
- because the PDF component raises an error otherwise.
7
- See https://github.com/datagouv/cdata/pull/611
8
- -->
9
- <PDF
10
- :src="props.resource.url"
11
- :show-progress="true"
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, defineAsyncComponent, onMounted, ref } from 'vue'
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
- const PDF = defineAsyncComponent(() =>
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 pdfData = ref<boolean>(false)
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
- // Test if the PDF URL is accessible
119
- const response = await fetch(props.resource.url, { method: 'HEAD' })
120
- // const response = await fetch('/test-data.pdf') // For testing locally without CORS issues
121
- if (!response.ok) {
122
- throw new Error(`HTTP error! status: ${response.status}`)
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
- // If the URL is accessible, set pdfData to true
126
- // The PDF component will handle the actual loading
127
- pdfData.value = true
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 testing PDF URL:', err)
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="shink-0 size-6" />
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="shink-0 size-6" />
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" />