@datagouv/components-next 1.0.2-dev.11 → 1.0.2-dev.111

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 (104) hide show
  1. package/assets/main.css +4 -0
  2. package/dist/{Control-DuZJdKV_.js → Control-ZFh5ta_U.js} +1 -1
  3. package/dist/{Datafair.client-8haHXl47.js → Datafair.client-CKB2P_X1.js} +1 -1
  4. package/dist/{Event--kp8kMdJ.js → Event-DSQcW7OF.js} +24 -24
  5. package/dist/{Image-34hvypZI.js → Image-BijNEG0p.js} +6 -6
  6. package/dist/JsonPreview.client-Bx11-jfT.js +40 -0
  7. package/dist/{Map-BjUnLyj8.js → Map-BUtPf5GN.js} +756 -756
  8. package/dist/{MapContainer.client-l6HuXTHR.js → MapContainer.client-CdZSeT_L.js} +37 -38
  9. package/dist/{OSM-s40W6sQ2.js → OSM-D4MTdBtk.js} +2 -2
  10. package/dist/{PdfPreview.client-4OueK-2Z.js → PdfPreview.client-Bh9lP-qU.js} +822 -850
  11. package/dist/{Pmtiles.client-4j3VTYkz.js → Pmtiles.client-Bi46wN14.js} +1 -1
  12. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-BnC7vWGP.js +61 -0
  13. package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-hJQIqcZm.js} +2 -2
  14. package/dist/{Tile-DbNFNPfU.js → Tile-Dcl7oIVu.js} +35 -35
  15. package/dist/{TileImage-BsXBxMtq.js → TileImage-BJeHipMX.js} +4 -4
  16. package/dist/{View-BR92hTWP.js → View-xp_P_OHw.js} +412 -401
  17. package/dist/XmlPreview.client-oFAOv828.js +34 -0
  18. package/dist/{common-PJfpC179.js → common-BjQlan3k.js} +36 -36
  19. package/dist/components-next.css +6 -6
  20. package/dist/components-next.js +165 -142
  21. package/dist/components.css +1 -1
  22. package/dist/{index-CVTIoZQ0.js → index-CxCuKQ81.js} +32886 -27183
  23. package/dist/main-CQ9ZQG7n.js +73607 -0
  24. package/dist/{proj-DsetBcW7.js → proj-CsNo9yH1.js} +532 -512
  25. package/dist/{tilecoord-Db24Px13.js → tilecoord-A0fLnBZr.js} +28 -28
  26. package/dist/{vue3-xml-viewer.common-CWer_T5-.js → vue3-xml-viewer.common-B9qp90K_.js} +1 -1
  27. package/package.json +25 -11
  28. package/src/chart.ts +5 -0
  29. package/src/components/ActivityList/ActivityList.vue +3 -2
  30. package/src/components/Chart/ChartViewer.vue +226 -0
  31. package/src/components/Chart/ChartViewerWrapper.vue +170 -0
  32. package/src/components/DataserviceCard.vue +3 -0
  33. package/src/components/DatasetCard.vue +9 -4
  34. package/src/components/Form/Listbox.vue +101 -0
  35. package/src/components/Form/SearchableSelect.vue +2 -1
  36. package/src/components/InfiniteLoader.vue +53 -0
  37. package/src/components/ObjectCardHeader.vue +11 -4
  38. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  39. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  40. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  41. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  42. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  43. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  44. package/src/components/OpenApiViewer/openapi.ts +150 -0
  45. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  46. package/src/components/Pagination.vue +8 -5
  47. package/src/components/RadioInput.vue +7 -2
  48. package/src/components/ReadMore.vue +1 -1
  49. package/src/components/ResourceAccordion/DataStructure.vue +11 -33
  50. package/src/components/ResourceAccordion/Downloads.vue +160 -0
  51. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
  52. package/src/components/ResourceAccordion/MapContainer.client.vue +1 -3
  53. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  54. package/src/components/ResourceAccordion/PdfPreview.client.vue +24 -87
  55. package/src/components/ResourceAccordion/Preview.vue +11 -11
  56. package/src/components/ResourceAccordion/PreviewWrapper.vue +82 -0
  57. package/src/components/ResourceAccordion/ResourceAccordion.vue +11 -110
  58. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
  59. package/src/components/ResourceExplorer/ResourceExplorer.vue +14 -10
  60. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +2 -2
  61. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +50 -148
  62. package/src/components/ResourceExplorer/ResourceSelector.vue +113 -0
  63. package/src/components/ReuseCard.vue +12 -4
  64. package/src/components/Search/GlobalSearch.vue +201 -113
  65. package/src/components/Search/SearchInput.vue +5 -4
  66. package/src/components/TabularExplorer/TabularCell.vue +51 -0
  67. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  68. package/src/components/TabularExplorer/TabularExplorer.vue +973 -0
  69. package/src/components/TabularExplorer/TabularFilterContent.vue +351 -0
  70. package/src/components/TabularExplorer/TabularFilterPopover.vue +111 -0
  71. package/src/components/TabularExplorer/types.ts +83 -0
  72. package/src/composables/useHasTabularData.ts +13 -0
  73. package/src/composables/useMetrics.ts +1 -1
  74. package/src/composables/useResourceCapabilities.ts +1 -1
  75. package/src/composables/useSearchFilter.ts +118 -0
  76. package/src/composables/useStableQueryParams.ts +38 -6
  77. package/src/composables/useTabularProfile.ts +70 -0
  78. package/src/config.ts +20 -3
  79. package/src/functions/activities.ts +3 -3
  80. package/src/functions/api.ts +9 -37
  81. package/src/functions/api.types.ts +1 -0
  82. package/src/functions/charts.ts +68 -0
  83. package/src/functions/datasets.ts +0 -17
  84. package/src/functions/metrics.ts +6 -4
  85. package/src/functions/resources.ts +56 -1
  86. package/src/functions/tabular.ts +60 -0
  87. package/src/functions/tabularApi.ts +138 -11
  88. package/src/main.ts +90 -9
  89. package/src/types/dataservices.ts +2 -0
  90. package/src/types/pages.ts +0 -5
  91. package/src/types/posts.ts +2 -2
  92. package/src/types/reports.ts +5 -1
  93. package/src/types/search.ts +63 -1
  94. package/src/types/site.ts +5 -3
  95. package/src/types/ui.ts +2 -0
  96. package/src/types/users.ts +2 -1
  97. package/src/types/visualizations.ts +89 -0
  98. package/assets/swagger-themes/newspaper.css +0 -1670
  99. package/dist/JsonPreview.client-D53pj9Cw.js +0 -72
  100. package/dist/Swagger.client-DPBmsH9q.js +0 -4
  101. package/dist/XmlPreview.client-XElkoA4F.js +0 -64
  102. package/dist/main-BbT-LUXy.js +0 -105854
  103. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  104. package/src/functions/pagination.ts +0 -9
@@ -49,45 +49,23 @@
49
49
  </template>
50
50
 
51
51
  <script setup lang="ts">
52
- import { onMounted, ref } from 'vue'
52
+ import { computed } from 'vue'
53
53
  import type { Resource } from '../../types/resources'
54
- import { useGetProfile } from '../../functions/tabularApi'
55
54
  import { useTranslation } from '../../composables/useTranslation'
55
+ import { injectTabularProfile } from '../../composables/useTabularProfile'
56
56
  import PreviewLoader from './PreviewLoader.vue'
57
57
 
58
58
  const props = defineProps<{ resource: Resource }>()
59
- const getProfile = useGetProfile()
60
59
  const { t } = useTranslation()
61
60
 
62
- type ColumnInfo = {
63
- score: number
64
- format: string
65
- python_type: string
66
- }
61
+ // Profile is shared with sibling components (e.g. TabularExplorer) via
62
+ // `provideTabularProfile` in the parent. Falls back to a local fetch
63
+ // when no parent provides it (standalone usage).
64
+ const { data: profileData, status } = await injectTabularProfile(() => props.resource.id)
67
65
 
68
- const columns = ref<Array<string>>([])
69
- const columnsInfo = ref<Record<string, ColumnInfo>>({})
70
- const loading = ref(true)
71
- const hasError = ref(false)
72
- const hasColumnInfo = ref(false)
73
-
74
- onMounted(async () => {
75
- try {
76
- const response = await getProfile(props.resource.id) // Assurez-vous que cette fonction retourne bien les données attendues
77
- if ('profile' in response && response.profile) {
78
- columns.value = Object.keys(response.profile.columns)
79
- columnsInfo.value = response.profile.columns
80
- hasColumnInfo.value = true
81
- loading.value = false
82
- }
83
- else {
84
- hasError.value = true
85
- loading.value = false
86
- }
87
- }
88
- catch {
89
- hasError.value = true
90
- loading.value = false
91
- }
92
- })
66
+ const loading = computed(() => status.value === 'idle' || status.value === 'pending')
67
+ const hasError = computed(() => status.value === 'error')
68
+ const hasColumnInfo = computed(() => !!profileData.value?.profile?.columns)
69
+ const columns = computed(() => profileData.value?.profile ? Object.keys(profileData.value.profile.columns) : [])
70
+ const columnsInfo = computed(() => profileData.value?.profile?.columns ?? {})
93
71
  </script>
@@ -0,0 +1,160 @@
1
+ <template>
2
+ <dl class="fr-pl-0">
3
+ <dt
4
+ v-if="resource.format === 'url'"
5
+ class="font-bold fr-text--sm fr-mb-0"
6
+ >
7
+ {{ t("URL d'origine") }}
8
+ </dt>
9
+ <dt
10
+ v-else
11
+ class="font-bold fr-text--sm fr-mb-0"
12
+ >
13
+ {{ t('Format original') }}
14
+ </dt>
15
+ <dd class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center">
16
+ <span
17
+ v-if="resource.format === 'url'"
18
+ class="inline-flex items-center max-w-full"
19
+ >
20
+ <a
21
+ :href="resource.latest"
22
+ class="fr-link no-icon-after truncate"
23
+ rel="ugc nofollow noopener"
24
+ target="_blank"
25
+ @click="trackEvent('Jeux de données', 'Télécharger un fichier', 'Bouton : télécharger un fichier')"
26
+ >
27
+ {{ resource.url }}
28
+ </a>
29
+ <span class="fr-ml-1v fr-icon-external-link-line fr-icon--sm shrink-0" />
30
+ </span>
31
+ <span v-else>
32
+ <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
33
+ <a
34
+ :href="resource.latest"
35
+ class="fr-link"
36
+ rel="ugc nofollow noopener"
37
+ @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${resource.format}`)"
38
+ >
39
+ <span>{{ t('Format {format}', { format: resource.format }) }}<span v-if="resourceFilesize"> - {{ filesize(resourceFilesize) }}</span></span>
40
+ </a>
41
+ </span>
42
+ <CopyButton
43
+ :label="t('Copier le lien')"
44
+ :copied-label="t('Lien copié !')"
45
+ :text="resource.latest"
46
+ class="relative"
47
+ />
48
+ </dd>
49
+ <template v-if="generatedFormats.length">
50
+ <dt class="font-bold fr-text--sm fr-mb-0">
51
+ {{ t('Formats générés automatiquement par {platform} (dernière mise à jour {date})', { platform: config.name, date: conversionsLastUpdate }) }}
52
+ </dt>
53
+ <dd
54
+ v-for="generatedFormat in generatedFormats"
55
+ :key="generatedFormat.format"
56
+ class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
57
+ >
58
+ <span>
59
+ <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
60
+ <a
61
+ :href="generatedFormat.url"
62
+ class="fr-link"
63
+ rel="ugc nofollow noopener"
64
+ @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${generatedFormat.format}`)"
65
+ >
66
+ <span>{{ t('Format {format}', { format: generatedFormat.format }) }}<span v-if="generatedFormat.size"> - {{ filesize(generatedFormat.size) }}</span></span>
67
+ </a>
68
+ </span>
69
+ <CopyButton
70
+ :label="t('Copier le lien')"
71
+ :copied-label="t('Lien copié !')"
72
+ :text="generatedFormat.url"
73
+ class="relative"
74
+ />
75
+ </dd>
76
+ </template>
77
+ <template v-if="wfsFormats.length">
78
+ <dt class="font-bold fr-text--sm fr-mb-0">
79
+ <div class="flex gap-1 items-center">
80
+ {{ t('Formats exportés depuis le service WFS') }}
81
+ <span v-if="defaultWfsProjection"> ({{ t('projection {crs}', { crs: defaultWfsProjection }) }})</span>
82
+ <Tooltip>
83
+ <RiInformationLine
84
+ class="flex-none size-4"
85
+ :aria-label="t(`Le lien de téléchargement interroge directement le flux WFS distant. Le nombre de features téléchargées peut être limité.`)"
86
+ aria-hidden="true"
87
+ />
88
+ <template #tooltip>
89
+ <p class="text-sm font-normal mb-0">
90
+ {{ t(`Le lien de téléchargement interroge directement le flux WFS distant.`) }}
91
+ </p>
92
+ <p class="text-sm font-normal mb-0">
93
+ {{ t(`Le nombre de features téléchargées peut être limité.`) }}
94
+ </p>
95
+ </template>
96
+ </Tooltip>
97
+ </div>
98
+ </dt>
99
+ <dd
100
+ v-for="wfsFormat in wfsFormats"
101
+ :key="wfsFormat.format"
102
+ class="text-sm pl-0 mb-4 text-gray-medium h-8 flex flex-wrap items-center"
103
+ >
104
+ <span>
105
+ <span class="text-datagouv fr-icon-download-line fr-icon--sm fr-mr-1v fr-mt-1v" />
106
+ <a
107
+ :href="wfsFormat.url"
108
+ class="fr-link"
109
+ rel="ugc nofollow noopener"
110
+ @click="trackEvent('Jeux de données', 'Télécharger un fichier', `Bouton : format ${wfsFormat.format}`)"
111
+ >
112
+ <span>{{ t('Format {format}', { format: wfsFormat.format }) }}</span>
113
+ </a>
114
+ </span>
115
+ <CopyButton
116
+ :label="t('Copier le lien')"
117
+ :copied-label="t('Lien copié !')"
118
+ :text="wfsFormat.url"
119
+ class="relative"
120
+ />
121
+ </dd>
122
+ </template>
123
+ </dl>
124
+ </template>
125
+
126
+ <script setup lang="ts">
127
+ import { computed } from 'vue'
128
+ import { RiInformationLine } from '@remixicon/vue'
129
+ import CopyButton from '../CopyButton.vue'
130
+ import Tooltip from '../Tooltip.vue'
131
+ import { filesize } from '../../functions/helpers'
132
+ import { getResourceFilesize } from '../../functions/resources'
133
+ import { trackEvent } from '../../functions/matomo'
134
+ import { useComponentsConfig } from '../../config'
135
+ import { useFormatDate } from '../../functions/dates'
136
+ import { useTranslation } from '../../composables/useTranslation'
137
+ import { useResourceCapabilities } from '../../composables/useResourceCapabilities'
138
+ import type { Resource } from '../../types/resources'
139
+ import type { Dataset, DatasetV2 } from '../../types/datasets'
140
+
141
+ const props = defineProps<{
142
+ resource: Resource
143
+ dataset: Dataset | DatasetV2
144
+ }>()
145
+
146
+ const { t } = useTranslation()
147
+ const config = useComponentsConfig()
148
+ const { formatRelativeIfRecentDate } = useFormatDate()
149
+
150
+ const { generatedFormats, wfsFormats, defaultWfsProjection } = useResourceCapabilities(
151
+ () => props.resource,
152
+ () => props.dataset,
153
+ )
154
+
155
+ const resourceFilesize = computed(() => getResourceFilesize(props.resource))
156
+
157
+ const conversionsLastUpdate = computed(() =>
158
+ formatRelativeIfRecentDate(props.resource.extras['analysis:parsing:finished_at'] as string | undefined),
159
+ )
160
+ </script>
@@ -1,48 +1,31 @@
1
1
  <template>
2
- <div class="fr-text--xs">
3
- <div v-if="jsonData">
4
- <JsonViewer
5
- :value="jsonData"
6
- boxed
7
- sort
8
- theme="light"
9
- :max-depth="3"
10
- :expand-depth="2"
11
- :indent-width="2"
12
- />
13
- </div>
14
- <div
15
- v-else-if="loading"
16
- class="text-gray-medium"
17
- >
18
- {{ t("Chargement de l'aperçu JSON...") }}
19
- </div>
20
- <PreviewUnavailable v-else-if="fileTooLarge">
21
- {{ fileSizeBytes
22
- ? t("Le fichier JSON est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.")
23
- : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
24
- }}
25
- </PreviewUnavailable>
26
- <PreviewUnavailable v-else-if="error === 'network'">
27
- {{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le depuis l'onglet Téléchargements.") }}
28
- </PreviewUnavailable>
29
- <PreviewUnavailable v-else-if="error">
30
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
31
- </PreviewUnavailable>
32
- </div>
2
+ <PreviewWrapper
3
+ v-slot="{ data }"
4
+ file-type="JSON"
5
+ :resource="resource"
6
+ :max-size="config.maxJsonPreviewCharSize"
7
+ :load="load"
8
+ >
9
+ <JsonViewer
10
+ :value="data"
11
+ boxed
12
+ sort
13
+ theme="light"
14
+ :max-depth="3"
15
+ :expand-depth="2"
16
+ :indent-width="2"
17
+ />
18
+ </PreviewWrapper>
33
19
  </template>
34
20
 
35
21
  <script setup lang="ts">
36
- import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
22
+ import { defineAsyncComponent } from 'vue'
37
23
  import { useComponentsConfig } from '../../config'
38
- import PreviewUnavailable from './PreviewUnavailable.vue'
24
+ import PreviewWrapper from './PreviewWrapper.vue'
39
25
  import type { Resource } from '../../types/resources'
40
- import { useTranslation } from '../../composables/useTranslation'
41
- import { getResourceFilesize } from '../../functions/datasets'
42
26
 
43
27
  const JsonViewer = defineAsyncComponent(() =>
44
28
  import('vue3-json-viewer').then((module) => {
45
- // Import CSS when component loads
46
29
  import('vue3-json-viewer/dist/vue3-json-viewer.css')
47
30
  return module.JsonViewer
48
31
  }),
@@ -53,74 +36,10 @@ const props = defineProps<{
53
36
  }>()
54
37
 
55
38
  const config = useComponentsConfig()
56
- const { t } = useTranslation()
57
39
 
58
- const jsonData = ref<unknown>(null)
59
- const loading = ref(false)
60
- const error = ref<string | null>(null)
61
- const fileTooLarge = ref(false)
62
-
63
- const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
64
-
65
- const shouldLoadJson = computed(() => {
66
- const size = fileSizeBytes.value
67
- if (!size) {
68
- // If we don't know the size, don't risk loading a potentially huge file
69
- return false
70
- }
71
-
72
- // Check if maxJsonPreviewCharSize is configured
73
- if (!config.maxJsonPreviewCharSize) {
74
- // If no limit is set, don't load unknown files
75
- return false
76
- }
77
-
78
- // Convert maxJsonPreviewCharSize from characters to bytes (rough estimate)
79
- // Assuming average 1 byte per character for JSON
80
- const maxByteSize = config.maxJsonPreviewCharSize
81
-
82
- return size <= maxByteSize
83
- })
84
-
85
- const fetchJsonData = async () => {
86
- // Check if file is too large or size is unknown before making the request
87
- if (!shouldLoadJson.value) {
88
- fileTooLarge.value = true
89
- return
90
- }
91
-
92
- loading.value = true
93
- error.value = null
94
-
95
- try {
96
- const response = await fetch(props.resource.url)
97
- // const response = await fetch('/test-data.json') // For testing locally without CORS issues
98
- if (!response.ok) {
99
- throw new Error(`HTTP error! status: ${response.status}`)
100
- }
101
- const data = await response.json()
102
-
103
- // Use the original data directly - let the JSON viewer handle large files
104
- jsonData.value = data
105
- }
106
- catch (err) {
107
- console.error('Error loading JSON:', err)
108
-
109
- if (err instanceof TypeError) {
110
- error.value = 'network'
111
- }
112
- else {
113
- error.value = 'generic'
114
- }
115
-
116
- jsonData.value = null
117
- }
118
- finally {
119
- loading.value = false
120
- }
40
+ const load = async () => {
41
+ const response = await fetch(props.resource.url)
42
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
43
+ return response.json()
121
44
  }
122
-
123
- onMounted(() => {
124
- fetchJsonData()
125
- })
126
45
  </script>
@@ -89,10 +89,8 @@ async function displayMap() {
89
89
 
90
90
  const attributions = new GeoportalAttribution({
91
91
  position: 'bottom-right',
92
- // collapsed option is ignored by the library, thus the override below
93
- // see https://github.com/IGNF/geopf-extensions-openlayers/issues/497
92
+ collapsed: false,
94
93
  })
95
- attributions.setCollapsed(false)
96
94
  map.addControl(attributions)
97
95
 
98
96
  const layerImport = new LayerImport({
@@ -7,9 +7,8 @@ import DescriptionTerm from '../DescriptionTerm.vue'
7
7
  import { useFormatDate } from '../../functions/dates'
8
8
  import { filesize } from '../../functions/helpers'
9
9
  import ExtraAccordion from '../ExtraAccordion.vue'
10
- import { getResourceTitleId, getResourceLabel } from '../../functions/resources'
10
+ import { getResourceTitleId, getResourceLabel, getResourceFilesize } from '../../functions/resources'
11
11
  import { useTranslation } from '../../composables/useTranslation'
12
- import { getResourceFilesize } from '../../functions/datasets'
13
12
 
14
13
  const props = defineProps<{
15
14
  resource: Resource
@@ -1,48 +1,34 @@
1
1
  <template>
2
- <div class="text-xs">
2
+ <PreviewWrapper
3
+ v-slot="{ data }"
4
+ file-type="PDF"
5
+ :resource="resource"
6
+ :max-size="config.maxPdfPreviewByteSize"
7
+ :load="load"
8
+ @loaded="renderAllPages"
9
+ >
3
10
  <div
4
- v-if="pdfReady"
5
11
  ref="containerRef"
6
12
  class="w-full overflow-y-auto max-h-[80vh] space-y-3"
7
13
  >
8
14
  <canvas
9
- v-for="page in totalPages"
15
+ v-for="page in (data as PDFDocumentProxy).numPages"
10
16
  :key="page"
11
17
  :ref="(el) => setCanvasRef(el as HTMLCanvasElement, page)"
12
18
  class="w-full"
13
19
  />
14
20
  </div>
15
- <div
16
- v-else-if="loading"
17
- class="text-gray-medium"
18
- >
19
- {{ t("Chargement de l'aperçu PDF...") }}
20
- </div>
21
- <PreviewUnavailable v-else-if="fileTooLarge">
22
- {{ fileSizeBytes
23
- ? t("Le fichier PDF est trop volumineux pour être prévisualisé. Téléchargez-le depuis l'onglet Téléchargements.")
24
- : t("La taille du fichier est inconnue, l'aperçu n'est pas disponible. Téléchargez-le depuis l'onglet Téléchargements.")
25
- }}
26
- </PreviewUnavailable>
27
- <PreviewUnavailable v-else-if="error === 'network'">
28
- {{ t("Ce fichier est hébergé sur un site externe qui ne permet pas la prévisualisation. Téléchargez-le depuis l'onglet Téléchargements.") }}
29
- </PreviewUnavailable>
30
- <PreviewUnavailable v-else-if="error">
31
- {{ t("L'aperçu de ce fichier n'a pas pu être chargé. Téléchargez-le depuis l'onglet Téléchargements.") }}
32
- </PreviewUnavailable>
33
- </div>
21
+ </PreviewWrapper>
34
22
  </template>
35
23
 
36
24
  <script setup lang="ts">
37
- import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
25
+ import { onBeforeUnmount, ref } from 'vue'
38
26
  import * as pdfjsLib from 'pdfjs-dist'
39
27
  import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url'
40
28
  import type { PDFDocumentProxy } from 'pdfjs-dist'
41
- import PreviewUnavailable from './PreviewUnavailable.vue'
29
+ import PreviewWrapper from './PreviewWrapper.vue'
42
30
  import { useComponentsConfig } from '../../config'
43
31
  import type { Resource } from '../../types/resources'
44
- import { useTranslation } from '../../composables/useTranslation'
45
- import { getResourceFilesize } from '../../functions/datasets'
46
32
 
47
33
  pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker
48
34
 
@@ -51,15 +37,8 @@ const props = defineProps<{
51
37
  }>()
52
38
 
53
39
  const config = useComponentsConfig()
54
- const { t } = useTranslation()
55
40
 
56
41
  const containerRef = ref<HTMLElement | null>(null)
57
- const pdfReady = ref(false)
58
- const loading = ref(false)
59
- const error = ref<string | null>(null)
60
- const fileTooLarge = ref(false)
61
- const totalPages = ref(0)
62
-
63
42
  let pdfDoc: PDFDocumentProxy | null = null
64
43
  const canvasRefs = new Map<number, HTMLCanvasElement>()
65
44
 
@@ -95,64 +74,22 @@ async function renderPage(pageNum: number) {
95
74
  }).promise
96
75
  }
97
76
 
98
- const fileSizeBytes = computed(() => getResourceFilesize(props.resource))
99
-
100
- const shouldLoadPdf = computed(() => {
101
- const size = fileSizeBytes.value
102
- if (!size) {
103
- // If we don't know the size, don't risk loading a potentially huge file
104
- return false
105
- }
106
-
107
- // Use maxPdfPreviewByteSize from config, fallback to 10 MB if not set
108
- const maxByteSize = config.maxPdfPreviewByteSize ?? 10_000_000
109
- return size <= maxByteSize
110
- })
111
-
112
- const loadPdf = async () => {
113
- if (!shouldLoadPdf.value) {
114
- fileTooLarge.value = true
115
- return
116
- }
117
-
118
- loading.value = true
119
- error.value = null
120
-
121
- try {
122
- const loadingTask = pdfjsLib.getDocument({
123
- url: props.resource.url,
124
- isEvalSupported: false,
125
- })
126
-
127
- pdfDoc = await loadingTask.promise
128
- totalPages.value = pdfDoc.numPages
129
- pdfReady.value = true
130
-
131
- await nextTick()
77
+ const load = async () => {
78
+ const loadingTask = pdfjsLib.getDocument({
79
+ url: props.resource.url,
80
+ isEvalSupported: false,
81
+ })
82
+ pdfDoc = await loadingTask.promise
83
+ return pdfDoc
84
+ }
132
85
 
133
- for (let i = 1; i <= pdfDoc.numPages; i++) {
134
- await renderPage(i)
135
- }
136
- }
137
- catch (err) {
138
- console.error('Error loading PDF:', err)
139
-
140
- if (err instanceof TypeError) {
141
- error.value = 'network'
142
- }
143
- else {
144
- error.value = 'generic'
145
- }
146
- }
147
- finally {
148
- loading.value = false
86
+ const renderAllPages = async () => {
87
+ if (!pdfDoc) return
88
+ for (let i = 1; i <= pdfDoc.numPages; i++) {
89
+ await renderPage(i)
149
90
  }
150
91
  }
151
92
 
152
- onMounted(() => {
153
- loadPdf()
154
- })
155
-
156
93
  onBeforeUnmount(() => {
157
94
  pdfDoc?.destroy()
158
95
  })
@@ -48,7 +48,7 @@
48
48
  >
49
49
  <BrandedButton
50
50
  color="tertiary"
51
- :icon="isSortedBy(col) && sortConfig && sortConfig.type == 'asc' ? RiArrowUpLine : RiArrowDownLine"
51
+ :icon="isSortedBy(col) && sortConfig && sortConfig.direction === 'asc' ? RiArrowUpLine : RiArrowDownLine"
52
52
  icon-right
53
53
  size="xs"
54
54
  @click="sortByField(col)"
@@ -56,7 +56,7 @@
56
56
  <!-- There is a weird bug with `sr-only`, I needed to add a relative parent to avoid full page x scrolling into the void… -->
57
57
  <span class="relative">
58
58
  {{ col }}
59
- <span class="sr-only">{{ sortConfig && sortConfig.type == 'desc' ? t("Trier par ordre croissant") : t("Trier par ordre décroissant") }}</span>
59
+ <span class="sr-only">{{ sortConfig && sortConfig.direction === 'desc' ? t("Trier par ordre croissant") : t("Trier par ordre décroissant") }}</span>
60
60
  </span>
61
61
  </BrandedButton>
62
62
  </th>
@@ -122,7 +122,7 @@ const rows = ref<Array<Record<string, unknown>>>([])
122
122
  const columns = ref<Array<string>>([])
123
123
  const loading = ref(true)
124
124
  const hasError = ref(false)
125
- const sortConfig = ref<SortConfig>(null)
125
+ const sortConfig = ref<SortConfig | null>(null)
126
126
  const rowCount = ref(0)
127
127
  const config = useComponentsConfig()
128
128
  const pageSize = computed(() => config.tabularApiPageSize || 15)
@@ -138,11 +138,11 @@ function isSortedBy(col: string) {
138
138
  /**
139
139
  * Retrieve preview necessary infos
140
140
  */
141
- async function getTableInfos(page: number, sortConfig?: SortConfig) {
141
+ async function getTableInfos(page: number, sortConfig?: SortConfig | null) {
142
142
  try {
143
143
  // Check that this function return wanted data
144
144
  const response = await getData(config, props.resource.id, page, sortConfig)
145
- if ('data' in response && response.data && response.data.length > 0) {
145
+ if ('data' in response && response.data && 0 in response.data) {
146
146
  // Update existing rows
147
147
  rows.value = response.data
148
148
  columns.value = Object.keys(response.data[0]).filter(item => item !== '__id')
@@ -172,24 +172,24 @@ function changePage(page: number) {
172
172
  * Sort by a specific column
173
173
  */
174
174
  function sortByField(col: string) {
175
- if (sortConfig.value && sortConfig.value.column == col) {
176
- if (sortConfig.value.type == 'asc') {
177
- sortConfig.value.type = 'desc'
175
+ if (sortConfig.value && sortConfig.value.column === col) {
176
+ if (sortConfig.value.direction === 'asc') {
177
+ sortConfig.value.direction = 'desc'
178
178
  }
179
179
  else {
180
- sortConfig.value.type = 'asc'
180
+ sortConfig.value.direction = 'asc'
181
181
  }
182
182
  }
183
183
  else {
184
184
  if (!sortConfig.value) {
185
185
  sortConfig.value = {
186
186
  column: col,
187
- type: 'asc',
187
+ direction: 'asc',
188
188
  }
189
189
  }
190
190
  else {
191
191
  sortConfig.value.column = col
192
- sortConfig.value.type = 'asc'
192
+ sortConfig.value.direction = 'asc'
193
193
  }
194
194
  }
195
195
  currentPage.value = 1