@datagouv/components-next 0.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/README.md +150 -0
- package/assets/main.css +136 -0
- package/assets/placeholders/author.png +0 -0
- package/assets/placeholders/dataset.png +0 -0
- package/assets/placeholders/news.png +0 -0
- package/assets/placeholders/organization.png +0 -0
- package/assets/placeholders/reuse.png +0 -0
- package/assets/tailwind.config.js +24 -0
- package/dist/components.css +2 -0
- package/dist/locales/de.js +155 -0
- package/dist/locales/en.js +155 -0
- package/dist/locales/es.js +155 -0
- package/dist/locales/fr.js +155 -0
- package/dist/locales/it.js +155 -0
- package/dist/locales/pt.js +155 -0
- package/dist/locales/sr.js +155 -0
- package/package.json +72 -0
- package/src/components/AppLink.vue +51 -0
- package/src/components/Avatar.vue +27 -0
- package/src/components/AvatarWithName.vue +26 -0
- package/src/components/BannerAction.vue +39 -0
- package/src/components/BrandedButton.vue +170 -0
- package/src/components/CopyButton.vue +84 -0
- package/src/components/DataserviceCard.vue +184 -0
- package/src/components/DatasetCard.vue +198 -0
- package/src/components/DatasetInformationPanel.vue +210 -0
- package/src/components/DatasetQuality.vue +68 -0
- package/src/components/DatasetQualityInline.vue +32 -0
- package/src/components/DatasetQualityItem.vue +32 -0
- package/src/components/DatasetQualityItemWarning.vue +21 -0
- package/src/components/DatasetQualityScore.vue +35 -0
- package/src/components/DatasetQualityTooltipContent.vue +79 -0
- package/src/components/DescriptionDetails.vue +23 -0
- package/src/components/DescriptionList/DescriptionDetails.stories.ts +43 -0
- package/src/components/DescriptionList/DescriptionList.stories.ts +47 -0
- package/src/components/DescriptionList/DescriptionTerm.stories.ts +28 -0
- package/src/components/DescriptionList.vue +8 -0
- package/src/components/DescriptionTerm.vue +8 -0
- package/src/components/ExtraAccordion.vue +78 -0
- package/src/components/Icons/Archive.vue +21 -0
- package/src/components/Icons/Code.vue +21 -0
- package/src/components/Icons/Documentation.vue +21 -0
- package/src/components/Icons/File.vue +21 -0
- package/src/components/Icons/Image.vue +7 -0
- package/src/components/Icons/Link.vue +21 -0
- package/src/components/Icons/Table.vue +21 -0
- package/src/components/OrganizationCard.vue +68 -0
- package/src/components/OrganizationNameWithCertificate.vue +45 -0
- package/src/components/OwnerType.vue +43 -0
- package/src/components/OwnerTypeIcon.vue +18 -0
- package/src/components/Pagination.vue +205 -0
- package/src/components/Placeholder.vue +29 -0
- package/src/components/ReadMore.vue +107 -0
- package/src/components/ResourceAccordion/DataStructure.vue +87 -0
- package/src/components/ResourceAccordion/EditButton.vue +34 -0
- package/src/components/ResourceAccordion/Metadata.vue +171 -0
- package/src/components/ResourceAccordion/Preview.vue +229 -0
- package/src/components/ResourceAccordion/PreviewLoader.vue +148 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +484 -0
- package/src/components/ResourceAccordion/ResourceIcon.vue +16 -0
- package/src/components/ResourceAccordion/SchemaBadge.vue +148 -0
- package/src/components/ResourceAccordion/SchemaLoader.vue +30 -0
- package/src/components/ResourceAccordion/Swagger.vue +46 -0
- package/src/components/ResourceAccordion/france.svg +1 -0
- package/src/components/ReuseCard.vue +106 -0
- package/src/components/ReuseDetails.vue +45 -0
- package/src/components/SimpleBanner.vue +24 -0
- package/src/components/SmallChart.vue +149 -0
- package/src/components/StatBox.vue +100 -0
- package/src/components/Tabs/Tab.vue +62 -0
- package/src/components/Tabs/TabGroup.vue +20 -0
- package/src/components/Tabs/TabList.vue +15 -0
- package/src/components/Tabs/TabPanel.vue +7 -0
- package/src/components/Tabs/TabPanels.vue +7 -0
- package/src/components/Toggletip.vue +62 -0
- package/src/components/ToggletipButton.vue +14 -0
- package/src/composables/useActiveDescendant.ts +103 -0
- package/src/composables/useReuseType.ts +14 -0
- package/src/config.ts +33 -0
- package/src/functions/api.ts +96 -0
- package/src/functions/api.types.ts +41 -0
- package/src/functions/config.ts +12 -0
- package/src/functions/datasets.ts +24 -0
- package/src/functions/dates.ts +85 -0
- package/src/functions/helpers.ts +38 -0
- package/src/functions/markdown.ts +47 -0
- package/src/functions/matomo.ts +3 -0
- package/src/functions/organizations.ts +85 -0
- package/src/functions/owned.ts +11 -0
- package/src/functions/resources.ts +99 -0
- package/src/functions/reuses.ts +28 -0
- package/src/functions/schemas.ts +96 -0
- package/src/functions/tabularApi.ts +27 -0
- package/src/functions/users.ts +7 -0
- package/src/locales/de.json +154 -0
- package/src/locales/en.json +154 -0
- package/src/locales/es.json +154 -0
- package/src/locales/fr.json +154 -0
- package/src/locales/it.json +154 -0
- package/src/locales/pt.json +154 -0
- package/src/locales/sr.json +154 -0
- package/src/main.ts +147 -0
- package/src/types/badges.ts +5 -0
- package/src/types/contact_point.ts +7 -0
- package/src/types/dataservices.ts +68 -0
- package/src/types/datasets.ts +80 -0
- package/src/types/frequency.ts +6 -0
- package/src/types/granularity.ts +6 -0
- package/src/types/harvest.ts +3 -0
- package/src/types/keyboard.ts +1 -0
- package/src/types/licenses.ts +9 -0
- package/src/types/organizations.ts +41 -0
- package/src/types/owned.ts +9 -0
- package/src/types/resources.ts +37 -0
- package/src/types/reuses.ts +49 -0
- package/src/types/site.ts +23 -0
- package/src/types/topics.ts +20 -0
- package/src/types/ui.ts +3 -0
- package/src/types/users.ts +10 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
v-if="totalResults > pageSize"
|
|
4
|
+
role="navigation"
|
|
5
|
+
class="fr-pagination fr-pagination--centered"
|
|
6
|
+
:aria-label="t('Pagination')"
|
|
7
|
+
>
|
|
8
|
+
<ul class="fr-pagination__list">
|
|
9
|
+
<li>
|
|
10
|
+
<a
|
|
11
|
+
:href="getHref(1)"
|
|
12
|
+
class="fr-pagination__link fr-pagination__link--first"
|
|
13
|
+
data-testid="first-page"
|
|
14
|
+
@click.prevent="onClick(1)"
|
|
15
|
+
>
|
|
16
|
+
{{ t('First page') }}
|
|
17
|
+
</a>
|
|
18
|
+
</li>
|
|
19
|
+
<li>
|
|
20
|
+
<a
|
|
21
|
+
:href="getHref(page - 1)"
|
|
22
|
+
class="fr-pagination__link fr-pagination__link--prev fr-pagination__link--lg-label"
|
|
23
|
+
data-testid="previous-page"
|
|
24
|
+
@click.prevent="previousPage"
|
|
25
|
+
>
|
|
26
|
+
{{ t('Previous page') }}
|
|
27
|
+
</a>
|
|
28
|
+
</li>
|
|
29
|
+
<li>
|
|
30
|
+
<a
|
|
31
|
+
:aria-current="page === 1 ? 'page' : undefined"
|
|
32
|
+
:href="getHref(1)"
|
|
33
|
+
class="fr-pagination__link"
|
|
34
|
+
:class="{ 'fr-hidden fr-unhidden-sm': page > 1 }"
|
|
35
|
+
:title="t('Page {nb}', { nb: 1 })"
|
|
36
|
+
:data-testid="1"
|
|
37
|
+
@click.prevent="onClick(1)"
|
|
38
|
+
>
|
|
39
|
+
1
|
|
40
|
+
</a>
|
|
41
|
+
</li>
|
|
42
|
+
<li
|
|
43
|
+
v-for="(index, arrayIndex) in visiblePages"
|
|
44
|
+
:key="arrayIndex"
|
|
45
|
+
>
|
|
46
|
+
<a
|
|
47
|
+
v-if="index"
|
|
48
|
+
class="fr-pagination__link"
|
|
49
|
+
:class="{ 'fr-hidden fr-unhidden-lg': index < page - 1 || index > page + 1 }"
|
|
50
|
+
:aria-current="page === index ? 'page' : undefined"
|
|
51
|
+
:href="getHref(index)"
|
|
52
|
+
:title="t('Page {nb}', { nb: index })"
|
|
53
|
+
:data-testid="index"
|
|
54
|
+
@click.prevent="onClick(index)"
|
|
55
|
+
>
|
|
56
|
+
{{ index }}
|
|
57
|
+
</a>
|
|
58
|
+
<a
|
|
59
|
+
v-else
|
|
60
|
+
class="fr-pagination__link fr-hidden fr-unhidden-lg"
|
|
61
|
+
>
|
|
62
|
+
…
|
|
63
|
+
</a>
|
|
64
|
+
</li>
|
|
65
|
+
<li>
|
|
66
|
+
<a
|
|
67
|
+
class="fr-pagination__link"
|
|
68
|
+
:aria-current="page === pageCount ? 'page' : undefined"
|
|
69
|
+
:href="getHref(pageCount)"
|
|
70
|
+
:title="t('Page {nb}', { nb: pageCount })"
|
|
71
|
+
:data-testid="pageCount"
|
|
72
|
+
@click.prevent="onClick(pageCount)"
|
|
73
|
+
>
|
|
74
|
+
{{ pageCount }}
|
|
75
|
+
</a>
|
|
76
|
+
</li>
|
|
77
|
+
<li>
|
|
78
|
+
<a
|
|
79
|
+
class="fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label"
|
|
80
|
+
:href="getHref(page + 1)"
|
|
81
|
+
data-testid="next-page"
|
|
82
|
+
@click.prevent="nextPage"
|
|
83
|
+
>
|
|
84
|
+
{{ t('Next page') }}
|
|
85
|
+
</a>
|
|
86
|
+
</li>
|
|
87
|
+
<li>
|
|
88
|
+
<a
|
|
89
|
+
class="fr-pagination__link fr-pagination__link--last"
|
|
90
|
+
:href="getHref(pageCount)"
|
|
91
|
+
data-testid="last-page"
|
|
92
|
+
@click.prevent="onClick(pageCount)"
|
|
93
|
+
>
|
|
94
|
+
{{ t('Last page') }}
|
|
95
|
+
</a>
|
|
96
|
+
</li>
|
|
97
|
+
</ul>
|
|
98
|
+
</nav>
|
|
99
|
+
</template>
|
|
100
|
+
|
|
101
|
+
<script setup lang="ts">
|
|
102
|
+
import { computed } from 'vue'
|
|
103
|
+
import { useI18n } from 'vue-i18n'
|
|
104
|
+
|
|
105
|
+
type Props = {
|
|
106
|
+
/**
|
|
107
|
+
* The current page.
|
|
108
|
+
*/
|
|
109
|
+
page?: number
|
|
110
|
+
/**
|
|
111
|
+
* The page size.
|
|
112
|
+
*/
|
|
113
|
+
pageSize?: number
|
|
114
|
+
/**
|
|
115
|
+
* Customize the links used
|
|
116
|
+
*/
|
|
117
|
+
link?: (page: number) => string
|
|
118
|
+
/**
|
|
119
|
+
* The number of items in the collection. It's used to calculated the number of pages.
|
|
120
|
+
*/
|
|
121
|
+
totalResults: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const emit = defineEmits<{
|
|
125
|
+
(event: 'change', page: number): void
|
|
126
|
+
}>()
|
|
127
|
+
|
|
128
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
129
|
+
page: 1,
|
|
130
|
+
pageSize: 20,
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
const PAGES_AROUND = 3
|
|
134
|
+
|
|
135
|
+
function range(size: number, startAt = 1) {
|
|
136
|
+
return [...Array(size).keys()].map(i => i + startAt)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getPages(pageCount: number) {
|
|
140
|
+
return range(pageCount)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getPagesShown(pages: { length: number }, currentPage: number) {
|
|
144
|
+
return Math.min(
|
|
145
|
+
PAGES_AROUND * 2 + 1,
|
|
146
|
+
pages.length - 2,
|
|
147
|
+
PAGES_AROUND + currentPage - 1,
|
|
148
|
+
PAGES_AROUND + pages.length - currentPage,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getStartPage(currentPage: number) {
|
|
153
|
+
return Math.max(
|
|
154
|
+
currentPage - PAGES_AROUND, // we want to start 3 pages before the current one
|
|
155
|
+
2, // we don't want to start below page 2
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getVisiblePages(currentPage: number, pageCount: number) {
|
|
160
|
+
const pages = getPages(pageCount)
|
|
161
|
+
const start = getStartPage(currentPage)
|
|
162
|
+
if (pageCount <= 2) {
|
|
163
|
+
return []
|
|
164
|
+
}
|
|
165
|
+
const pagination: Array<number | null> = range(getPagesShown(pages, currentPage), start)
|
|
166
|
+
if (!pagination.includes(2)) {
|
|
167
|
+
pagination.unshift(null)
|
|
168
|
+
}
|
|
169
|
+
if (!pagination.includes(pageCount - 1)) {
|
|
170
|
+
pagination.push(null)
|
|
171
|
+
}
|
|
172
|
+
return pagination
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const { t } = useI18n()
|
|
176
|
+
const pageCount = computed(() => Math.ceil(props.totalResults / props.pageSize))
|
|
177
|
+
const visiblePages = computed(() => getVisiblePages(props.page, pageCount.value))
|
|
178
|
+
|
|
179
|
+
function onClick(index: number) {
|
|
180
|
+
if (index !== props.page) {
|
|
181
|
+
return emit('change', index)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function nextPage() {
|
|
186
|
+
const index = props.page + 1
|
|
187
|
+
if (index <= pageCount.value) {
|
|
188
|
+
return emit('change', index)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function previousPage() {
|
|
193
|
+
const index = props.page - 1
|
|
194
|
+
if (index > 0) {
|
|
195
|
+
return emit('change', index)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function getHref(forPage: number) {
|
|
200
|
+
if (forPage < 1 || forPage > pageCount.value) {
|
|
201
|
+
return undefined
|
|
202
|
+
}
|
|
203
|
+
return props.page === forPage ? undefined : (props.link ? props.link(forPage) : '#')
|
|
204
|
+
}
|
|
205
|
+
</script>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<img
|
|
3
|
+
loading="lazy"
|
|
4
|
+
:src="path"
|
|
5
|
+
:alt="alternativeTextForDefinedImageOnly"
|
|
6
|
+
:width="size"
|
|
7
|
+
:height="size"
|
|
8
|
+
v-bind="$attrs"
|
|
9
|
+
>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
import { computedAsync } from '@vueuse/core'
|
|
15
|
+
|
|
16
|
+
const props = defineProps<{
|
|
17
|
+
type: 'author' | 'dataset' | 'news' | 'organization' | 'reuse'
|
|
18
|
+
src?: string | null
|
|
19
|
+
alt?: string
|
|
20
|
+
size: number
|
|
21
|
+
}>()
|
|
22
|
+
const placeholderUrl = async () => {
|
|
23
|
+
const module = await import(`../../assets/placeholders/${props.type}.png`)
|
|
24
|
+
return props.src ? props.src : module.default
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const alternativeTextForDefinedImageOnly = computed(() => props.src ? props.alt : '')
|
|
28
|
+
const path = computedAsync(() => placeholderUrl())
|
|
29
|
+
</script>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative">
|
|
3
|
+
<div
|
|
4
|
+
ref="readMoreRef"
|
|
5
|
+
class="overflow-hidden"
|
|
6
|
+
:style="{ height: containerHeight + 'px' }"
|
|
7
|
+
>
|
|
8
|
+
<div ref="containerRef">
|
|
9
|
+
<slot />
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div
|
|
13
|
+
v-if="readMoreRequired"
|
|
14
|
+
class=" bottom-0 w-full cursor-pointer text-center"
|
|
15
|
+
:class="{
|
|
16
|
+
'relative pt-2.5': expanded,
|
|
17
|
+
'absolute pt-20 bg-linear-to-b from-white/0 via-70% via-white/100 to-white/100': !expanded,
|
|
18
|
+
}"
|
|
19
|
+
@click.stop="toggle"
|
|
20
|
+
>
|
|
21
|
+
<BrandedButton
|
|
22
|
+
color="primary-softer"
|
|
23
|
+
@click.stop="toggle"
|
|
24
|
+
>
|
|
25
|
+
<template v-if="expanded">
|
|
26
|
+
{{ $t("Read less") }}
|
|
27
|
+
</template>
|
|
28
|
+
<template v-else>
|
|
29
|
+
{{ $t("Read more") }}
|
|
30
|
+
</template>
|
|
31
|
+
</BrandedButton>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import { templateRef, useElementSize } from '@vueuse/core'
|
|
38
|
+
import { ref, watch } from 'vue'
|
|
39
|
+
import { easing, tween, styler } from 'popmotion'
|
|
40
|
+
import BrandedButton from './BrandedButton.vue'
|
|
41
|
+
|
|
42
|
+
const props = withDefaults(defineProps<{
|
|
43
|
+
maxHeight?: string
|
|
44
|
+
}>(), {
|
|
45
|
+
maxHeight: '',
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const DEFAULT_HEIGHT = 284
|
|
49
|
+
const expanded = ref(false)
|
|
50
|
+
const readMoreRequired = ref(false)
|
|
51
|
+
const containerRef = templateRef<HTMLElement | null>('containerRef')
|
|
52
|
+
const readMoreRef = templateRef<HTMLElement | null>('readMoreRef')
|
|
53
|
+
const { height } = useElementSize(containerRef)
|
|
54
|
+
const containerHeight = ref(DEFAULT_HEIGHT)
|
|
55
|
+
const getHeight = (elt: Element) => {
|
|
56
|
+
const style = getComputedStyle(elt)
|
|
57
|
+
return parseFloat(style.getPropertyValue('height'))
|
|
58
|
+
+ parseFloat(style.getPropertyValue('margin-top'))
|
|
59
|
+
+ parseFloat(style.getPropertyValue('margin-bottom'))
|
|
60
|
+
}
|
|
61
|
+
const getDefaultHeight = () => {
|
|
62
|
+
const elementMaxHeight = document.querySelector(`[data-read-more-max-height="${props.maxHeight}"]`)
|
|
63
|
+
if (!elementMaxHeight) {
|
|
64
|
+
return DEFAULT_HEIGHT
|
|
65
|
+
}
|
|
66
|
+
return Array.from(elementMaxHeight.children)
|
|
67
|
+
.map(getHeight)
|
|
68
|
+
.reduce((total, height) => total + height, 0)
|
|
69
|
+
}
|
|
70
|
+
const updateReadMoreHeight = (height: number) => {
|
|
71
|
+
containerHeight.value = getDefaultHeight()
|
|
72
|
+
readMoreRequired.value = height > containerHeight.value
|
|
73
|
+
if (!readMoreRequired.value) {
|
|
74
|
+
containerHeight.value = height
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const toggle = () => {
|
|
78
|
+
if (!readMoreRef.value) {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
const divStyler = styler(readMoreRef.value)
|
|
82
|
+
if (expanded.value) {
|
|
83
|
+
tween({
|
|
84
|
+
from: { height: readMoreRef.value.scrollHeight },
|
|
85
|
+
to: { height: getDefaultHeight() },
|
|
86
|
+
duration: 300,
|
|
87
|
+
ease: easing.anticipate,
|
|
88
|
+
}).start({
|
|
89
|
+
update: divStyler.set,
|
|
90
|
+
complete: () => readMoreRef.value?.scrollIntoView({ behavior: 'smooth' }),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
tween({
|
|
95
|
+
from: { height: getDefaultHeight() },
|
|
96
|
+
to: { height: readMoreRef.value.scrollHeight },
|
|
97
|
+
duration: 300,
|
|
98
|
+
ease: easing.anticipate,
|
|
99
|
+
}).start({
|
|
100
|
+
update: divStyler.set,
|
|
101
|
+
complete: () => divStyler.set({ height: 'auto' }),
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
expanded.value = !expanded.value
|
|
105
|
+
}
|
|
106
|
+
watch(height, updateReadMoreHeight)
|
|
107
|
+
</script>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="hasError"
|
|
4
|
+
class="bg-warning-lightest text-warning-dark p-3 mb-4"
|
|
5
|
+
>
|
|
6
|
+
<p class="fr-grid-row fr-m-0">
|
|
7
|
+
<span
|
|
8
|
+
class="fr-icon-warning-line"
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
/>
|
|
11
|
+
{{ $t("The data structure of this file failed to load.") }}
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
<PreviewLoader v-else-if="loading" />
|
|
15
|
+
<div
|
|
16
|
+
v-else
|
|
17
|
+
class="fr-grid-row fr-grid-row--gutters"
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
v-if="!hasColumnInfo"
|
|
21
|
+
class="bg-warning-lightest text-warning-dark p-3 mb-4"
|
|
22
|
+
>
|
|
23
|
+
<p class="fr-grid-row fr-m-0">
|
|
24
|
+
<span
|
|
25
|
+
class="fr-icon-warning-line"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
/>
|
|
28
|
+
{{ $t("No data structure found for this file.") }}
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
<template v-if="hasColumnInfo">
|
|
32
|
+
<div
|
|
33
|
+
v-for="(column, index) in columns"
|
|
34
|
+
:key="index"
|
|
35
|
+
class="fr-col-12 fr-col-sm-6 fr-col-md-4 fr-col-lg-3"
|
|
36
|
+
>
|
|
37
|
+
<h5 class="fr-text--sm fr-text--bold fr-mt-0 fr-mb-1v">
|
|
38
|
+
{{ column }}
|
|
39
|
+
</h5>
|
|
40
|
+
<code class="code">
|
|
41
|
+
{{ columnsInfo[column].format }}
|
|
42
|
+
</code>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { onMounted, ref } from 'vue'
|
|
50
|
+
import type { Resource } from '../../types/resources'
|
|
51
|
+
import { getProfile } from '../../functions/tabularApi'
|
|
52
|
+
import PreviewLoader from './PreviewLoader.vue'
|
|
53
|
+
|
|
54
|
+
const props = defineProps<{ resource: Resource }>()
|
|
55
|
+
|
|
56
|
+
type ColumnInfo = {
|
|
57
|
+
score: number
|
|
58
|
+
format: string
|
|
59
|
+
python_type: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const columns = ref<Array<string>>([])
|
|
63
|
+
const columnsInfo = ref<Record<string, ColumnInfo>>({})
|
|
64
|
+
const loading = ref(true)
|
|
65
|
+
const hasError = ref(false)
|
|
66
|
+
const hasColumnInfo = ref(false)
|
|
67
|
+
|
|
68
|
+
onMounted(async () => {
|
|
69
|
+
try {
|
|
70
|
+
const { data } = await getProfile(props.resource.id) // Assurez-vous que cette fonction retourne bien les données attendues
|
|
71
|
+
if ('profile' in data && data.profile) {
|
|
72
|
+
columns.value = Object.keys(data.profile.columns)
|
|
73
|
+
columnsInfo.value = data.profile.columns
|
|
74
|
+
hasColumnInfo.value = true
|
|
75
|
+
loading.value = false
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
hasError.value = true
|
|
79
|
+
loading.value = false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
hasError.value = true
|
|
84
|
+
loading.value = false
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BrandedButton
|
|
3
|
+
:href="adminUrl"
|
|
4
|
+
icon-only
|
|
5
|
+
:icon="RiPencilLine"
|
|
6
|
+
color="warning"
|
|
7
|
+
data-testid="edit-button"
|
|
8
|
+
>
|
|
9
|
+
{{ t("Edit file") }}
|
|
10
|
+
</BrandedButton>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { useI18n } from 'vue-i18n'
|
|
15
|
+
import { RiPencilLine } from '@remixicon/vue'
|
|
16
|
+
import { useComponentsConfig } from '../../config'
|
|
17
|
+
import BrandedButton from '../BrandedButton.vue'
|
|
18
|
+
|
|
19
|
+
type Props = {
|
|
20
|
+
datasetId: string
|
|
21
|
+
isCommunityResource?: boolean
|
|
22
|
+
resourceId: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
26
|
+
isCommunityResource: false,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const config = useComponentsConfig()
|
|
30
|
+
|
|
31
|
+
const { t } = useI18n()
|
|
32
|
+
const resourceType = props.isCommunityResource ? 'community-resource' : 'resource'
|
|
33
|
+
const adminUrl = `${config.baseUrl}/dataset/${props.datasetId}/${resourceType}/${props.resourceId}`
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useI18n } from 'vue-i18n'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import type { Resource } from '../../types/resources'
|
|
5
|
+
import CopyButton from '../CopyButton.vue'
|
|
6
|
+
import DescriptionDetails from '../DescriptionDetails.vue'
|
|
7
|
+
import DescriptionList from '../DescriptionList.vue'
|
|
8
|
+
import DescriptionTerm from '../DescriptionTerm.vue'
|
|
9
|
+
import { formatDate } from '../../functions/dates'
|
|
10
|
+
import { filesize } from '../../functions/helpers'
|
|
11
|
+
import ExtraAccordion from '../ExtraAccordion.vue'
|
|
12
|
+
import { getResourceTitleId, getResourceLabel } from '../../functions/resources'
|
|
13
|
+
import { useComponentsConfig } from '../../config'
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
resource: Resource
|
|
17
|
+
}>()
|
|
18
|
+
|
|
19
|
+
const hasExtras = computed(() => Object.keys(props.resource.extras).length)
|
|
20
|
+
const resourceTitleId = computed(() => getResourceTitleId(props.resource))
|
|
21
|
+
|
|
22
|
+
const { t } = useI18n()
|
|
23
|
+
const config = useComponentsConfig()
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div>
|
|
28
|
+
<div class="flex gap-3rem flex-col-on-small">
|
|
29
|
+
<DescriptionList class="flex-1">
|
|
30
|
+
<DescriptionTerm>
|
|
31
|
+
{{ t('URL') }}
|
|
32
|
+
<CopyButton
|
|
33
|
+
:label="$t('Copy URL')"
|
|
34
|
+
:copied-label="$t('URL copied!')"
|
|
35
|
+
:text="resource.url"
|
|
36
|
+
:aria-describedby="resourceTitleId"
|
|
37
|
+
/>
|
|
38
|
+
</DescriptionTerm>
|
|
39
|
+
<DescriptionDetails :with-ellipsis="false">
|
|
40
|
+
<code class="code">
|
|
41
|
+
<a :href="resource.url"><component
|
|
42
|
+
:is="config.textClamp"
|
|
43
|
+
v-if="config && config.textClamp"
|
|
44
|
+
:max-lines="1"
|
|
45
|
+
:autoresize="true"
|
|
46
|
+
:text="resource.url"
|
|
47
|
+
/></a>
|
|
48
|
+
</code>
|
|
49
|
+
</DescriptionDetails>
|
|
50
|
+
<DescriptionTerm>
|
|
51
|
+
{{ t('Stable URL') }}
|
|
52
|
+
<CopyButton
|
|
53
|
+
:label="$t('Copy stable URL')"
|
|
54
|
+
:copied-label="$t('Stable URL copied!')"
|
|
55
|
+
:text="resource.latest"
|
|
56
|
+
:aria-describedby="resourceTitleId"
|
|
57
|
+
/>
|
|
58
|
+
</DescriptionTerm>
|
|
59
|
+
<DescriptionDetails :with-ellipsis="false">
|
|
60
|
+
<code class="code">
|
|
61
|
+
<a :href="resource.latest"><component
|
|
62
|
+
:is="config.textClamp"
|
|
63
|
+
v-if="config && config.textClamp"
|
|
64
|
+
:max-lines="1"
|
|
65
|
+
:autoresize="true"
|
|
66
|
+
:text="resource.latest"
|
|
67
|
+
/></a>
|
|
68
|
+
</code>
|
|
69
|
+
</DescriptionDetails>
|
|
70
|
+
<DescriptionTerm>
|
|
71
|
+
{{ t('Identifier') }}
|
|
72
|
+
<CopyButton
|
|
73
|
+
:label="$t('Copy ID')"
|
|
74
|
+
:copied-label="$t('ID copied!')"
|
|
75
|
+
:text="resource.id"
|
|
76
|
+
:aria-describedby="resourceTitleId"
|
|
77
|
+
/>
|
|
78
|
+
</DescriptionTerm>
|
|
79
|
+
<DescriptionDetails :with-ellipsis="false">
|
|
80
|
+
<code class="code">
|
|
81
|
+
<component
|
|
82
|
+
:is="config.textClamp"
|
|
83
|
+
v-if="config && config.textClamp"
|
|
84
|
+
:max-lines="1"
|
|
85
|
+
:autoresize="true"
|
|
86
|
+
:text="resource.id"
|
|
87
|
+
/>
|
|
88
|
+
</code>
|
|
89
|
+
</DescriptionDetails>
|
|
90
|
+
<template v-if="resource.checksum">
|
|
91
|
+
<DescriptionTerm>
|
|
92
|
+
{{ resource.checksum.type }}
|
|
93
|
+
<CopyButton
|
|
94
|
+
:label="$t('Copy checksum')"
|
|
95
|
+
:copied-label="$t('Checksum copied!')"
|
|
96
|
+
:text="resource.checksum.value"
|
|
97
|
+
:aria-describedby="resourceTitleId"
|
|
98
|
+
/>
|
|
99
|
+
</DescriptionTerm>
|
|
100
|
+
<DescriptionDetails :with-ellipsis="false">
|
|
101
|
+
<code class="code">
|
|
102
|
+
<component
|
|
103
|
+
:is="config.textClamp"
|
|
104
|
+
v-if="config && config.textClamp"
|
|
105
|
+
:max-lines="1"
|
|
106
|
+
:autoresize="true"
|
|
107
|
+
:text="resource.checksum.value"
|
|
108
|
+
/>
|
|
109
|
+
</code>
|
|
110
|
+
</DescriptionDetails>
|
|
111
|
+
</template>
|
|
112
|
+
</DescriptionList>
|
|
113
|
+
<DescriptionList style="flex-shrink: 0;">
|
|
114
|
+
<DescriptionTerm>{{ t('Created on') }}</DescriptionTerm>
|
|
115
|
+
<DescriptionDetails>
|
|
116
|
+
{{ formatDate(resource.created_at) }}
|
|
117
|
+
</DescriptionDetails>
|
|
118
|
+
<DescriptionTerm>{{ t('Modified on') }}</DescriptionTerm>
|
|
119
|
+
<DescriptionDetails>
|
|
120
|
+
{{ formatDate(resource.last_modified) }}
|
|
121
|
+
</DescriptionDetails>
|
|
122
|
+
</DescriptionList>
|
|
123
|
+
<DescriptionList style="flex-shrink: 0;">
|
|
124
|
+
<template v-if="resource.filesize">
|
|
125
|
+
<DescriptionTerm>{{ t('Size') }}</DescriptionTerm>
|
|
126
|
+
<DescriptionDetails>
|
|
127
|
+
{{ filesize(resource.filesize) }}
|
|
128
|
+
</DescriptionDetails>
|
|
129
|
+
</template>
|
|
130
|
+
<template v-if="resource.mime">
|
|
131
|
+
<DescriptionTerm>{{ t('Type') }}</DescriptionTerm>
|
|
132
|
+
<DescriptionDetails>
|
|
133
|
+
{{ getResourceLabel(resource.type) }}
|
|
134
|
+
</DescriptionDetails>
|
|
135
|
+
</template>
|
|
136
|
+
<template v-if="resource.mime">
|
|
137
|
+
<DescriptionTerm>{{ t('MIME Type') }}</DescriptionTerm>
|
|
138
|
+
<DescriptionDetails>
|
|
139
|
+
<code class="code text-overflow-ellipsis">{{ resource.mime }}</code>
|
|
140
|
+
</DescriptionDetails>
|
|
141
|
+
</template>
|
|
142
|
+
</DescriptionList>
|
|
143
|
+
</div>
|
|
144
|
+
<div>
|
|
145
|
+
<ExtraAccordion
|
|
146
|
+
v-if="hasExtras"
|
|
147
|
+
class="pt-6 mt-6 border-top border-gray-default"
|
|
148
|
+
:button-text="t('See extras')"
|
|
149
|
+
:title-text="t('Resource Extras')"
|
|
150
|
+
title-level="h5"
|
|
151
|
+
:extra="resource.extras"
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
<style scoped>
|
|
158
|
+
.gap-3rem {
|
|
159
|
+
gap: 3rem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.gap-3rem dl {
|
|
163
|
+
padding-inline-start: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@container (max-width: 600px) {
|
|
167
|
+
.flex-col-on-small {
|
|
168
|
+
flex-direction: column
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
</style>
|