@datagouv/components-next 0.2.0 → 1.0.0
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 +1 -1
- package/assets/main.css +56 -1
- package/dist/Control-BNCDn-8E.js +148 -0
- package/dist/{Datafair.client-x39O4yfF.js → Datafair.client-Dls5AHTE.js} +1 -1
- package/dist/Event-BOgJUhNR.js +738 -0
- package/dist/Image-BN-4XkIn.js +247 -0
- package/dist/{JsonPreview.client-BMsC5JcY.js → JsonPreview.client-DPDTs433.js} +14 -14
- package/dist/Map-BdT3i2C4.js +7609 -0
- package/dist/MapContainer.client-BdAzd7bj.js +105 -0
- package/dist/OSM-CamriM9b.js +71 -0
- package/dist/{PdfPreview.client-COOkEkRA.js → PdfPreview.client-CopqSDyt.js} +3 -3
- package/dist/{Pmtiles.client-BaiIo4VZ.js → Pmtiles.client-mF6xaOO_.js} +2 -2
- package/dist/ScaleLine-BiesrgOv.js +165 -0
- package/dist/Swagger.client-eJ7gpfZA.js +4 -0
- package/dist/Tile-DCuqwNOI.js +1206 -0
- package/dist/TileImage-CmZf8EdU.js +1067 -0
- package/dist/View-DcDc7N2K.js +2858 -0
- package/dist/{XmlPreview.client-CAdN0w_Y.js → XmlPreview.client-C0OgBkSq.js} +7 -7
- package/dist/common-C4rDcQpp.js +243 -0
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +153 -117
- package/dist/components.css +1 -1
- package/dist/{MapContainer.client-DeSo8EvG.js → index-BRGqW8aQ.js} +4975 -21416
- package/dist/leaflet-src-7m1mB8LI.js +6338 -0
- package/dist/{main-Dgri3TQL.js → main-CNHxAJ8J.js} +56758 -51450
- package/dist/proj-CKwYjU38.js +1569 -0
- package/dist/tilecoord-YW3qEH_j.js +884 -0
- package/dist/{vue3-xml-viewer.common-D6skc_Ai.js → vue3-xml-viewer.common-CmAdQfIy.js} +1 -1
- package/package.json +5 -1
- package/src/components/ActivityList/ActivityList.vue +6 -2
- package/src/components/AppLink.vue +4 -1
- package/src/components/Avatar.vue +2 -2
- package/src/components/AvatarWithName.vue +8 -4
- package/src/components/BouncingDots.vue +21 -0
- package/src/components/BrandedButton.vue +2 -0
- package/src/components/CopyButton.vue +19 -7
- package/src/components/DataserviceCard.vue +83 -118
- package/src/components/DatasetCard.vue +110 -171
- package/src/components/DatasetInformation/DatasetEmbedSection.vue +43 -0
- package/src/components/DatasetInformation/DatasetInformationSection.vue +73 -0
- package/src/components/DatasetInformation/DatasetSchemaSection.vue +74 -0
- package/src/components/DatasetInformation/DatasetSpatialSection.vue +59 -0
- package/src/components/DatasetInformation/DatasetTemporalitySection.vue +45 -0
- package/src/components/DatasetInformation/index.ts +5 -0
- package/src/components/DatasetQualityTooltipContent.vue +3 -3
- package/src/components/DescriptionList.vue +1 -4
- package/src/components/DescriptionListDetails.vue +5 -0
- package/src/components/DescriptionListTerm.vue +5 -0
- package/src/components/DiscussionMessageCard.vue +63 -0
- package/src/components/ExtraAccordion.vue +4 -4
- package/src/components/Form/BadgeSelect.vue +35 -0
- package/src/components/Form/FormatSelect.vue +28 -0
- package/src/components/Form/GeozoneSelect.vue +52 -0
- package/src/components/Form/GranularitySelect.vue +29 -0
- package/src/components/Form/LicenseSelect.vue +30 -0
- package/src/components/Form/OrganizationSelect.vue +62 -0
- package/src/components/Form/OrganizationTypeSelect.vue +34 -0
- package/src/components/Form/ReuseTopicSelect.vue +29 -0
- package/src/components/Form/SchemaSelect.vue +30 -0
- package/src/components/Form/SearchableSelect.vue +334 -0
- package/src/components/Form/SelectGroup.vue +132 -0
- package/src/components/Form/TagSelect.vue +38 -0
- package/src/components/LeafletMap.vue +31 -0
- package/src/components/LicenseBadge.vue +24 -0
- package/src/components/LoadingBlock.vue +23 -2
- package/src/components/MarkdownViewer.vue +3 -1
- package/src/components/ObjectCard.vue +42 -0
- package/src/components/ObjectCardBadge.vue +22 -0
- package/src/components/ObjectCardHeader.vue +35 -0
- package/src/components/ObjectCardOwner.vue +43 -0
- package/src/components/ObjectCardShortDescription.vue +28 -0
- package/src/components/OrganizationCard.vue +35 -20
- package/src/components/OrganizationLogo.vue +1 -1
- package/src/components/OrganizationNameWithCertificate.vue +13 -7
- package/src/components/OwnerTypeIcon.vue +1 -0
- package/src/components/Pagination.vue +1 -1
- package/src/components/Placeholder.vue +5 -2
- package/src/components/PostCard.vue +62 -0
- package/src/components/RadioGroup.vue +32 -0
- package/src/components/RadioInput.vue +64 -0
- package/src/components/ResourceAccordion/EditButton.vue +2 -3
- package/src/components/ResourceAccordion/MapContainer.client.vue +20 -16
- package/src/components/ResourceAccordion/Metadata.vue +11 -24
- package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
- package/src/components/ResourceAccordion/Preview.vue +1 -1
- package/src/components/ResourceAccordion/ResourceAccordion.vue +30 -20
- package/src/components/ResourceAccordion/ResourceIcon.vue +1 -0
- package/src/components/ResourceAccordion/SchemaBadge.vue +2 -2
- package/src/components/ResourceExplorer/ResourceExplorer.vue +243 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +116 -0
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +361 -0
- package/src/components/ReuseCard.vue +8 -28
- package/src/components/ReuseHorizontalCard.vue +80 -0
- package/src/components/Search/BasicAndAdvancedFilters.vue +49 -0
- package/src/components/Search/Filter/AccessTypeFilter.vue +37 -0
- package/src/components/Search/Filter/DatasetBadgeFilter.vue +40 -0
- package/src/components/Search/Filter/FilterButtonGroup.vue +78 -0
- package/src/components/Search/Filter/FormatFamilyFilter.vue +39 -0
- package/src/components/Search/Filter/LastUpdateRangeFilter.vue +37 -0
- package/src/components/Search/Filter/ProducerTypeFilter.vue +39 -0
- package/src/components/Search/Filter/ReuseTypeFilter.vue +42 -0
- package/src/components/Search/GlobalSearch.vue +611 -0
- package/src/components/Search/SearchInput.vue +63 -0
- package/src/components/Search/Sidemenu.vue +38 -0
- package/src/components/StatBox.vue +5 -5
- package/src/components/Tag.vue +30 -0
- package/src/components/Toggletip.vue +6 -2
- package/src/components/Tooltip.vue +2 -3
- package/src/components/TopicCard.vue +134 -0
- package/src/components/radioGroupContext.ts +9 -0
- package/src/composables/useDebouncedRef.ts +31 -0
- package/src/composables/useMetrics.ts +4 -3
- package/src/composables/useResourceCapabilities.ts +118 -0
- package/src/composables/useRouteQueryBoolean.ts +10 -0
- package/src/composables/useSelectModelSync.ts +89 -0
- package/src/composables/useStableQueryParams.ts +84 -0
- package/src/config.ts +4 -0
- package/src/functions/api.ts +17 -6
- package/src/functions/api.types.ts +4 -2
- package/src/functions/datasets.ts +1 -29
- package/src/functions/description.ts +33 -0
- package/src/functions/helpers.ts +11 -0
- package/src/functions/markdown.ts +60 -16
- package/src/functions/metrics.ts +33 -0
- package/src/functions/organizations.ts +5 -5
- package/src/main.ts +89 -7
- package/src/types/dataservices.ts +14 -12
- package/src/types/datasets.ts +20 -7
- package/src/types/discussions.ts +20 -0
- package/src/types/licenses.ts +3 -3
- package/src/types/organizations.ts +13 -1
- package/src/types/owned.ts +4 -2
- package/src/types/pages.ts +70 -0
- package/src/types/posts.ts +27 -0
- package/src/types/resources.ts +6 -0
- package/src/types/reuses.ts +14 -5
- package/src/types/search.ts +379 -0
- package/src/types/users.ts +12 -3
- package/dist/Swagger.client-CpLgaLg6.js +0 -4
- package/src/components/DatasetInformationPanel.vue +0 -211
package/src/functions/api.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ref, toValue, watchEffect, onMounted, type ComputedRef, type Ref } from 'vue'
|
|
2
2
|
import { ofetch } from 'ofetch'
|
|
3
3
|
import { useComponentsConfig } from '../config'
|
|
4
4
|
import { useTranslation } from '../composables/useTranslation'
|
|
@@ -23,11 +23,13 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
23
23
|
const execute = async () => {
|
|
24
24
|
const urlValue = toValue(url)
|
|
25
25
|
if (!urlValue) return
|
|
26
|
-
const
|
|
26
|
+
const params = toValue(options?.params)
|
|
27
|
+
const query = toValue(options?.query)
|
|
27
28
|
status.value = 'pending'
|
|
28
29
|
try {
|
|
29
30
|
data.value = await ofetch<DataT | null>(urlValue, {
|
|
30
31
|
baseURL: config.apiBase,
|
|
32
|
+
params: params ?? query,
|
|
31
33
|
onRequest(param) {
|
|
32
34
|
if (config.onRequest) {
|
|
33
35
|
if (Array.isArray(config.onRequest)) {
|
|
@@ -55,7 +57,6 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
55
57
|
onRequestError: config.onRequestError,
|
|
56
58
|
onResponse: config.onResponse,
|
|
57
59
|
onResponseError: config.onResponseError,
|
|
58
|
-
...fetchOptions,
|
|
59
60
|
})
|
|
60
61
|
status.value = 'success'
|
|
61
62
|
}
|
|
@@ -65,9 +66,19 @@ export async function useFetch<DataT, ErrorT = never>(
|
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// When server is false, only start watching after mount (client-side only)
|
|
70
|
+
if (options?.server === false) {
|
|
71
|
+
onMounted(() => {
|
|
72
|
+
watchEffect(async () => {
|
|
73
|
+
await execute()
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
watchEffect(async () => {
|
|
79
|
+
await execute()
|
|
80
|
+
})
|
|
81
|
+
}
|
|
71
82
|
|
|
72
83
|
return {
|
|
73
84
|
data,
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ComputedRef, Ref, WatchSource } from 'vue'
|
|
2
2
|
|
|
3
|
+
type MaybeRef<T> = T | Ref<T> | ComputedRef<T>
|
|
4
|
+
|
|
3
5
|
export type UseFetchOptions<DataT> = {
|
|
4
6
|
key?: string
|
|
5
7
|
method?: string
|
|
6
|
-
query?: Record<string, unknown
|
|
7
|
-
params?: Record<string, unknown
|
|
8
|
+
query?: MaybeRef<Record<string, unknown>>
|
|
9
|
+
params?: MaybeRef<Record<string, unknown>>
|
|
8
10
|
body?: RequestInit['body'] | Record<string, unknown>
|
|
9
11
|
headers?: Record<string, string> | [key: string, value: string][] | Headers
|
|
10
12
|
baseURL?: string
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { useComponentsConfig } from '../config'
|
|
2
2
|
import type { Dataset, DatasetV2 } from '../types/datasets'
|
|
3
3
|
import type { CommunityResource, Resource } from '../types/resources'
|
|
4
|
-
import { removeMarkdown } from './markdown'
|
|
5
|
-
|
|
6
|
-
// Dataset description constants
|
|
7
|
-
export const DESCRIPTION_SHORT_MAX_LENGTH = 200
|
|
8
|
-
export const DESCRIPTION_MIN_LENGTH = 200
|
|
9
4
|
|
|
10
5
|
function constructUrl(baseUrl: string, path: string): string {
|
|
11
6
|
const url = new URL(baseUrl)
|
|
@@ -25,30 +20,7 @@ export function isCommunityResource(resource: Resource | CommunityResource): boo
|
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
export function getResourceExternalUrl(dataset: Dataset | DatasetV2 | Omit<Dataset, 'resources' | 'community_resources'>, resource: Resource | CommunityResource): string {
|
|
28
|
-
return `${dataset.page}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Returns the short description to display.
|
|
33
|
-
* If description_short is provided, it is used.
|
|
34
|
-
* Otherwise, the first DESCRIPTION_SHORT_MAX_LENGTH characters of description are used.
|
|
35
|
-
*/
|
|
36
|
-
export async function getDescriptionShort(
|
|
37
|
-
description: string | null | undefined,
|
|
38
|
-
descriptionShort: string | null | undefined,
|
|
39
|
-
): Promise<string> {
|
|
40
|
-
if (descriptionShort?.trim()) {
|
|
41
|
-
return descriptionShort
|
|
42
|
-
}
|
|
43
|
-
if (description?.trim()) {
|
|
44
|
-
// description field is a markdown field that may contain HTML tags, so we should trim it
|
|
45
|
-
const plainText = (await removeMarkdown(description)).trim()
|
|
46
|
-
if (plainText.length > DESCRIPTION_SHORT_MAX_LENGTH) {
|
|
47
|
-
return `${plainText.substring(0, DESCRIPTION_SHORT_MAX_LENGTH - 1)}…`
|
|
48
|
-
}
|
|
49
|
-
return plainText
|
|
50
|
-
}
|
|
51
|
-
return ''
|
|
23
|
+
return `${dataset.page}${isCommunityResource(resource) ? '/community-resources' : ''}?resource_id=${resource.id}`
|
|
52
24
|
}
|
|
53
25
|
|
|
54
26
|
export function getResourceFilesize(resource: Resource): null | number {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { removeMarkdownSync } from './markdown'
|
|
2
|
+
|
|
3
|
+
// Dataset description constants
|
|
4
|
+
|
|
5
|
+
// Form validation (client-side rules)
|
|
6
|
+
export const DESCRIPTION_SHORT_MAX_LENGTH = 200 // max for `description_short` (+ truncation/output cap)
|
|
7
|
+
export const DESCRIPTION_MIN_LENGTH = 200 // min (recommendation) for `description` (not AI gating)
|
|
8
|
+
|
|
9
|
+
// AI gating (enable AI suggestions; not validation)
|
|
10
|
+
export const AI_SUGGESTION_MIN_DESCRIPTION_LENGTH = 200 // min `description` length to enable suggestions
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns the short description to display.
|
|
14
|
+
* If description_short is provided, it is used.
|
|
15
|
+
* Otherwise, the first DESCRIPTION_SHORT_MAX_LENGTH characters of description are used.
|
|
16
|
+
*/
|
|
17
|
+
export function getDescriptionShort({ description, descriptionShort }: {
|
|
18
|
+
description: string | null | undefined
|
|
19
|
+
descriptionShort?: string | null | undefined
|
|
20
|
+
}) {
|
|
21
|
+
if (descriptionShort?.trim()) {
|
|
22
|
+
return descriptionShort
|
|
23
|
+
}
|
|
24
|
+
if (description?.trim()) {
|
|
25
|
+
// description field is a markdown field that may contain HTML tags, so we should trim it
|
|
26
|
+
const plainText = removeMarkdownSync(description).trim()
|
|
27
|
+
if (plainText.length > DESCRIPTION_SHORT_MAX_LENGTH) {
|
|
28
|
+
return `${plainText.substring(0, DESCRIPTION_SHORT_MAX_LENGTH - 1)}…`
|
|
29
|
+
}
|
|
30
|
+
return plainText
|
|
31
|
+
}
|
|
32
|
+
return ''
|
|
33
|
+
}
|
package/src/functions/helpers.ts
CHANGED
|
@@ -37,3 +37,14 @@ export const summarize = (val: number, fractionDigits = 0) => {
|
|
|
37
37
|
}
|
|
38
38
|
return `${toFixedIfNotZero(val)}Y`
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
export const escapeCsvValue = (value: string | number | null | undefined): string => {
|
|
42
|
+
if (value === null || value === undefined || value === '') {
|
|
43
|
+
return ''
|
|
44
|
+
}
|
|
45
|
+
const stringValue = String(value)
|
|
46
|
+
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
|
|
47
|
+
return `"${stringValue.replace(/"/g, '""')}"`
|
|
48
|
+
}
|
|
49
|
+
return stringValue
|
|
50
|
+
}
|
|
@@ -16,26 +16,57 @@ import remarkGfm from 'remark-gfm'
|
|
|
16
16
|
import strip from 'strip-markdown'
|
|
17
17
|
|
|
18
18
|
// Copied from https://github.com/potato4d/rehype-plugin-image-native-lazy-loading/blob/v1.2.0/src/index.ts
|
|
19
|
-
function
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
function rehypeLazyLoad(this: Processor): Transformer {
|
|
20
|
+
return function transformer(htmlAST: Node): Node {
|
|
21
|
+
visit(htmlAST, 'element', function visitor(el: hast.Element) {
|
|
22
|
+
if (el.tagName !== 'img') {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
el.properties = {
|
|
26
|
+
...(el.properties || {}),
|
|
27
|
+
loading: 'lazy',
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
return htmlAST
|
|
28
31
|
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function rehypeNoHeadings(this: Processor): Transformer {
|
|
35
|
+
return function transformer(htmlAST: Node): Node {
|
|
36
|
+
visit(htmlAST, 'element', function visitor(el: hast.Element) {
|
|
37
|
+
if (el.tagName !== 'h1' && el.tagName !== 'h2' && el.tagName !== 'h3' && el.tagName !== 'h4' && el.tagName !== 'h5' && el.tagName !== 'h6') {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const classes = {
|
|
42
|
+
h1: 'text-3xl leading-8',
|
|
43
|
+
h2: 'text-2xl leading-7',
|
|
44
|
+
h3: 'text-xl leading-6',
|
|
45
|
+
h4: 'text-base',
|
|
46
|
+
h5: 'text-sm leading-6',
|
|
47
|
+
h6: 'text-sm leading-6',
|
|
48
|
+
}[el.tagName]
|
|
29
49
|
|
|
30
|
-
|
|
31
|
-
|
|
50
|
+
el.properties = {
|
|
51
|
+
...(el.properties || {}),
|
|
52
|
+
class: `font-extrabold ${classes}`,
|
|
53
|
+
}
|
|
54
|
+
el.tagName = 'div'
|
|
55
|
+
})
|
|
32
56
|
return htmlAST
|
|
33
57
|
}
|
|
34
|
-
|
|
35
|
-
return transformer
|
|
36
58
|
}
|
|
37
59
|
|
|
38
|
-
export function formatMarkdown(md: string, minDepth = 3) {
|
|
60
|
+
export function formatMarkdown(md: string, config: number | { minDepth: number, noHeadings: boolean } = 3) {
|
|
61
|
+
let minDepth: number
|
|
62
|
+
let noHeadings = false
|
|
63
|
+
if (typeof config === 'number') {
|
|
64
|
+
minDepth = config
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
minDepth = config.minDepth
|
|
68
|
+
noHeadings = config.noHeadings
|
|
69
|
+
}
|
|
39
70
|
const result = unified()
|
|
40
71
|
.use(behead, { minDepth: minDepth > 1 ? minDepth : undefined } as Options)
|
|
41
72
|
// Take Markdown as input and turn it into MD syntax tree
|
|
@@ -55,14 +86,14 @@ export function formatMarkdown(md: string, minDepth = 3) {
|
|
|
55
86
|
.use(rehypeSanitize)
|
|
56
87
|
// Serialize syntax tree to HTML
|
|
57
88
|
.use(rehypeStringify)
|
|
58
|
-
.use(
|
|
89
|
+
.use(noHeadings ? [rehypeLazyLoad, rehypeNoHeadings] : [rehypeLazyLoad])
|
|
59
90
|
// And finally, process the input
|
|
60
91
|
.processSync(md)
|
|
61
92
|
|
|
62
93
|
return String(result)
|
|
63
94
|
}
|
|
64
95
|
|
|
65
|
-
export async function
|
|
96
|
+
export async function removeMarkdownAsync(text: string) {
|
|
66
97
|
const file = await unified()
|
|
67
98
|
// Take Markdown as input and turn it into MD syntax tree
|
|
68
99
|
.use(remarkParse, { fragment: true })
|
|
@@ -73,6 +104,19 @@ export async function removeMarkdown(text: string) {
|
|
|
73
104
|
return String(file)
|
|
74
105
|
}
|
|
75
106
|
|
|
107
|
+
export function removeMarkdownSync(text: string) {
|
|
108
|
+
const file = unified()
|
|
109
|
+
// Take Markdown as input and turn it into MD syntax tree
|
|
110
|
+
.use(remarkParse, { fragment: true })
|
|
111
|
+
.use(remarkGfm)
|
|
112
|
+
.use(strip)
|
|
113
|
+
.use(remarkStringify)
|
|
114
|
+
.processSync(text)
|
|
115
|
+
return String(file)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { removeMarkdownAsync as removeMarkdown }
|
|
119
|
+
|
|
76
120
|
const prose = 'prose prose-neutral max-w-none prose-strong:text-gray-plain'
|
|
77
121
|
const proseSm = 'prose-p:text-sm prose-sm'
|
|
78
122
|
const proseTable = 'prose-table:bg-gray-some prose-table:overflow-visible prose-thead:border-b-2 prose-thead:border-black prose-tr:data-[is-header=true]:border-b-2 prose-tr:data-[is-header=true]:border-black prose-tr:even:bg-gray-lower prose-tr:border-b-0 *:prose-th:m-0 *:prose-td:m-0 prose-th:p-4 prose-td:p-4'
|
package/src/functions/metrics.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { escapeCsvValue } from './helpers'
|
|
2
|
+
import { ofetch } from 'ofetch'
|
|
3
|
+
import type { DatasetV2 } from '../types/datasets'
|
|
4
|
+
import type { PaginatedArray } from '../types/api'
|
|
5
|
+
|
|
1
6
|
export type OrganizationMetrics = {
|
|
2
7
|
downloads: Record<string, number>
|
|
3
8
|
downloadsTotal: number
|
|
@@ -114,6 +119,34 @@ export async function getDatasetMetrics(datasetId: string, metricsApi: string):
|
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
export async function createDatasetsForOrganizationMetricsUrl(organizationId: string, metricsApi: string, apiBase: string) {
|
|
123
|
+
let data = 'dataset_title,dataset_id,month,monthly_visit,monthly_download_resource\n'
|
|
124
|
+
|
|
125
|
+
// fetch datasets info from organization datasets
|
|
126
|
+
const datasets: Record<string, Record<string, string>> = {}
|
|
127
|
+
let datasetsUrl: string | null = `/api/2/datasets/?organization=${organizationId}&page_size=200`
|
|
128
|
+
while (datasetsUrl) {
|
|
129
|
+
const body: PaginatedArray<DatasetV2> = await ofetch(datasetsUrl, { baseURL: apiBase, credentials: 'include' })
|
|
130
|
+
datasetsUrl = body.next_page
|
|
131
|
+
for (const row of body.data) {
|
|
132
|
+
datasets[row.id] = { title: row.title }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// fetch datasets metrics for the organization
|
|
137
|
+
let metricsUrl: string | null = `${metricsApi}/api/datasets/data/?organization_id__exact=${organizationId}&metric_month__sort=desc&page_size=50`
|
|
138
|
+
while (metricsUrl) {
|
|
139
|
+
const body: { links: { next: string | null }, data: Array<{ dataset_id: string, metric_month: string, monthly_visit: number, monthly_download_resource: number }> } = await ofetch(metricsUrl)
|
|
140
|
+
metricsUrl = body.links.next
|
|
141
|
+
for (const row of body.data) {
|
|
142
|
+
const datasetTitle = datasets[row.dataset_id]?.title || ''
|
|
143
|
+
data += `${escapeCsvValue(datasetTitle)},${escapeCsvValue(row.dataset_id)},${escapeCsvValue(row.metric_month)},${row.monthly_visit},${row.monthly_download_resource}\n`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return URL.createObjectURL(new Blob([data], { type: 'text/csv' }))
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
export async function getDataserviceMetrics(dataserviceId: string, metricsApi: string): Promise<DataserviceMetrics> {
|
|
118
151
|
// Fetching last 12 months
|
|
119
152
|
const response = await fetch(`${metricsApi}/api/dataservices/data/?dataservice_id__exact=${dataserviceId}&metric_month__sort=desc&page_size=12`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Component } from 'vue'
|
|
2
2
|
import { RiBankLine, RiBuilding2Line, RiCommunityLine, RiGovernmentLine, RiUserLine } from '@remixicon/vue'
|
|
3
3
|
import { useComponentsConfig } from '../config'
|
|
4
|
-
import type { OrganizationReference } from '../types/organizations'
|
|
4
|
+
import type { Organization, OrganizationReference } from '../types/organizations'
|
|
5
5
|
import { useTranslation } from '../composables/useTranslation'
|
|
6
6
|
|
|
7
7
|
export const CERTIFIED = 'certified'
|
|
@@ -22,11 +22,11 @@ function constructUrl(baseUrl: string, path: string): string {
|
|
|
22
22
|
return url.toString()
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export function isType(organization: OrganizationReference, type: OrganizationTypes) {
|
|
25
|
+
export function isType(organization: Organization | OrganizationReference, type: OrganizationTypes) {
|
|
26
26
|
return hasBadge(organization, type)
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export function hasBadge(organization: OrganizationReference, kind: string) {
|
|
29
|
+
export function hasBadge(organization: Organization | OrganizationReference, kind: string) {
|
|
30
30
|
return organization.badges.some(badge => badge.kind === kind)
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -68,7 +68,7 @@ export function findOrganizationType(searched: OrganizationTypes | UserType) {
|
|
|
68
68
|
return getOrganizationTypes().find(type => type.type === searched)!
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export function getOrganizationType(organization: OrganizationReference): OrganizationTypes {
|
|
71
|
+
export function getOrganizationType(organization: Organization | OrganizationReference): OrganizationTypes {
|
|
72
72
|
if (isType(organization, LOCAL_AUTHORITY)) {
|
|
73
73
|
return LOCAL_AUTHORITY
|
|
74
74
|
}
|
|
@@ -86,7 +86,7 @@ export function getOrganizationType(organization: OrganizationReference): Organi
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export function isOrganizationCertified(organization: OrganizationReference | null): boolean {
|
|
89
|
+
export function isOrganizationCertified(organization: Organization | OrganizationReference | null): boolean {
|
|
90
90
|
if (!organization) return false
|
|
91
91
|
return hasBadge(organization, CERTIFIED) && (isType(organization, PUBLIC_SERVICE) || isType(organization, LOCAL_AUTHORITY))
|
|
92
92
|
}
|
package/src/main.ts
CHANGED
|
@@ -3,23 +3,28 @@ import type { Activity, ActivityKey } from './types/activity.js'
|
|
|
3
3
|
import type { PaginatedArray } from './types/api.js'
|
|
4
4
|
import type { ContactPoint, ContactPointRole } from './types/contact_point.js'
|
|
5
5
|
import type { Badge, Badges, TranslatedBadge } from './types/badges'
|
|
6
|
-
import type { Dataset, DatasetV2, DatasetV2WithFullObject, NewDataset, Quality, Rel } from './types/datasets'
|
|
7
|
-
import type { NewDataservice, Dataservice } from './types/dataservices'
|
|
6
|
+
import type { DatasetReference, Dataset, DatasetV2, DatasetV2WithFullObject, NewDataset, Quality, Rel } from './types/datasets'
|
|
7
|
+
import type { DataserviceReference, NewDataservice, Dataservice } from './types/dataservices'
|
|
8
8
|
import type { AccessType, AccessAudience, AccessAudienceCondition, AccessAudienceType, WithAccessType, AccessTypeForm } from './types/access_types'
|
|
9
9
|
import type { Frequency, Frequencies } from './types/frequency'
|
|
10
10
|
import type { Granularity, Granularities, SpatialZone } from './types/granularity'
|
|
11
11
|
import type { Harvest } from './types/harvest'
|
|
12
12
|
import type { License } from './types/licenses'
|
|
13
13
|
import type { Member, MemberRole, NewOrganization, Organization, OrganizationOrSuggest, OrganizationReference, OrganizationSuggest } from './types/organizations'
|
|
14
|
-
import type { Owned, OwnedWithId } from './types/owned'
|
|
15
|
-
import type {
|
|
14
|
+
import type { Owned, OwnedWithFullObject, OwnedWithId } from './types/owned'
|
|
15
|
+
import type { Comment, Thread } from './types/discussions'
|
|
16
|
+
import type { Page, PageBloc, ContentBloc, BlocWithTitle, DatasetsListBloc, DataservicesListBloc, ReusesListBloc, LinkInBloc, LinksListBloc, MarkdownBloc, AccordionItemBloc, AccordionListBloc, HeroBloc } from './types/pages'
|
|
17
|
+
import type { Post } from './types/posts'
|
|
18
|
+
import type { ReuseReference, NewReuse, Reuse, ReuseTopic, ReuseType } from './types/reuses'
|
|
16
19
|
import type { RegisteredSchema, Schema, SchemaDetails, SchemaField, SchemaPath, SchemaPublicationMode, SchemaResponseData, SchemaVersion, ValidataError } from './types/schemas'
|
|
17
20
|
import type { TopicV2, TopicElement, TopicElementClass, TopicElementRel } from './types/topics'
|
|
18
|
-
import type { CommunityResource, FileResourceFileType, RemoteResourceFileType, ResourceFileType, ResourceType, Resource } from './types/resources'
|
|
21
|
+
import type { CommunityResource, FileResourceFileType, RemoteResourceFileType, ResourceFileType, ResourceGroup, ResourceType, Resource } from './types/resources'
|
|
19
22
|
import type { Site } from './types/site'
|
|
20
23
|
import type { Weight, WellType } from './types/ui'
|
|
21
24
|
import type { User, UserReference } from './types/users'
|
|
22
25
|
import type { Report, ReportSubject, ReportReason } from './types/reports'
|
|
26
|
+
import type { GlobalSearchConfig, SearchType, SortOption } from './types/search'
|
|
27
|
+
import { getDefaultDatasetConfig, getDefaultDataserviceConfig, getDefaultReuseConfig, getDefaultGlobalSearchConfig, defaultDatasetSortOptions, defaultDataserviceSortOptions, defaultReuseSortOptions } from './types/search'
|
|
23
28
|
|
|
24
29
|
import ActivityList from './components/ActivityList/ActivityList.vue'
|
|
25
30
|
import UserActivityList from './components/ActivityList/UserActivityList.vue'
|
|
@@ -32,8 +37,14 @@ import BrandedButton from './components/BrandedButton.vue'
|
|
|
32
37
|
import CopyButton from './components/CopyButton.vue'
|
|
33
38
|
import DataserviceCard from './components/DataserviceCard.vue'
|
|
34
39
|
import DatasetCard from './components/DatasetCard.vue'
|
|
40
|
+
import DescriptionListTerm from './components/DescriptionListTerm.vue'
|
|
41
|
+
import DescriptionListDetails from './components/DescriptionListDetails.vue'
|
|
42
|
+
import DiscussionMessageCard from './components/DiscussionMessageCard.vue'
|
|
35
43
|
import DateRangeDetails from './components/DateRangeDetails.vue'
|
|
36
|
-
import
|
|
44
|
+
import { DatasetInformationSection, DatasetTemporalitySection, DatasetSpatialSection, DatasetSchemaSection, DatasetEmbedSection } from './components/DatasetInformation'
|
|
45
|
+
import LeafletMap from './components/LeafletMap.vue'
|
|
46
|
+
import LicenseBadge from './components/LicenseBadge.vue'
|
|
47
|
+
import Tag from './components/Tag.vue'
|
|
37
48
|
import DatasetQuality from './components/DatasetQuality.vue'
|
|
38
49
|
import DatasetQualityInline from './components/DatasetQualityInline.vue'
|
|
39
50
|
import DatasetQualityItem from './components/DatasetQualityItem.vue'
|
|
@@ -51,11 +62,18 @@ import OwnerTypeIcon from './components/OwnerTypeIcon.vue'
|
|
|
51
62
|
import PaddedContainer from './components/PaddedContainer.vue'
|
|
52
63
|
import Pagination from './components/Pagination.vue'
|
|
53
64
|
import Placeholder from './components/Placeholder.vue'
|
|
65
|
+
import RadioGroup from './components/RadioGroup.vue'
|
|
66
|
+
import RadioInput from './components/RadioInput.vue'
|
|
67
|
+
import PostCard from './components/PostCard.vue'
|
|
54
68
|
import ReadMore from './components/ReadMore.vue'
|
|
55
69
|
import ResourceAccordion from './components/ResourceAccordion/ResourceAccordion.vue'
|
|
56
70
|
import ResourceIcon from './components/ResourceAccordion/ResourceIcon.vue'
|
|
71
|
+
import ResourceExplorer from './components/ResourceExplorer/ResourceExplorer.vue'
|
|
72
|
+
import ResourceExplorerSidebar from './components/ResourceExplorer/ResourceExplorerSidebar.vue'
|
|
73
|
+
import ResourceExplorerViewer from './components/ResourceExplorer/ResourceExplorerViewer.vue'
|
|
57
74
|
import Swagger from './components/ResourceAccordion/Swagger.client.vue'
|
|
58
75
|
import ReuseCard from './components/ReuseCard.vue'
|
|
76
|
+
import ReuseHorizontalCard from './components/ReuseHorizontalCard.vue'
|
|
59
77
|
import ReuseDetails from './components/ReuseDetails.vue'
|
|
60
78
|
import SchemaCard from './components/SchemaCard.vue'
|
|
61
79
|
import SimpleBanner from './components/SimpleBanner.vue'
|
|
@@ -68,10 +86,17 @@ import TabPanel from './components/Tabs/TabPanel.vue'
|
|
|
68
86
|
import TabPanels from './components/Tabs/TabPanels.vue'
|
|
69
87
|
import Tooltip from './components/Tooltip.vue'
|
|
70
88
|
import Toggletip from './components/Toggletip.vue'
|
|
89
|
+
import TopicCard from './components/TopicCard.vue'
|
|
71
90
|
import TranslationT from './components/TranslationT.vue'
|
|
91
|
+
import GlobalSearch from './components/Search/GlobalSearch.vue'
|
|
92
|
+
import SearchInput from './components/Search/SearchInput.vue'
|
|
93
|
+
import SearchableSelect from './components/Form/SearchableSelect.vue'
|
|
94
|
+
import SelectGroup from './components/Form/SelectGroup.vue'
|
|
72
95
|
import type { UseFetchFunction } from './functions/api.types'
|
|
73
96
|
import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
|
|
74
97
|
|
|
98
|
+
export { Toaster, toast } from 'vue-sonner'
|
|
99
|
+
|
|
75
100
|
export * from './composables/useActiveDescendant'
|
|
76
101
|
export * from './composables/useMetrics'
|
|
77
102
|
export * from './composables/useReuseType'
|
|
@@ -80,6 +105,7 @@ export * from './composables/useTranslation'
|
|
|
80
105
|
export * from './functions/activities'
|
|
81
106
|
export * from './functions/datasets'
|
|
82
107
|
export * from './functions/dates'
|
|
108
|
+
export * from './functions/description'
|
|
83
109
|
export * from './functions/helpers'
|
|
84
110
|
export * from './functions/markdown'
|
|
85
111
|
export * from './functions/matomo'
|
|
@@ -95,6 +121,9 @@ export * from './functions/users'
|
|
|
95
121
|
export * from './types/access_types'
|
|
96
122
|
|
|
97
123
|
export type {
|
|
124
|
+
GlobalSearchConfig,
|
|
125
|
+
SearchType,
|
|
126
|
+
SortOption,
|
|
98
127
|
UseFetchFunction,
|
|
99
128
|
AccessType,
|
|
100
129
|
AccessAudience,
|
|
@@ -109,10 +138,13 @@ export type {
|
|
|
109
138
|
CommunityResource,
|
|
110
139
|
ContactPoint,
|
|
111
140
|
ContactPointRole,
|
|
141
|
+
DatasetReference,
|
|
112
142
|
Dataset,
|
|
113
143
|
DatasetV2,
|
|
114
144
|
DatasetV2WithFullObject,
|
|
145
|
+
DataserviceReference,
|
|
115
146
|
Dataservice,
|
|
147
|
+
Comment,
|
|
116
148
|
NewDataservice,
|
|
117
149
|
FileResourceFileType,
|
|
118
150
|
Frequency,
|
|
@@ -131,8 +163,24 @@ export type {
|
|
|
131
163
|
OrganizationSuggest,
|
|
132
164
|
OrganizationOrSuggest,
|
|
133
165
|
Owned,
|
|
166
|
+
OwnedWithFullObject,
|
|
134
167
|
OwnedWithId,
|
|
168
|
+
Page,
|
|
169
|
+
PageBloc,
|
|
170
|
+
ContentBloc,
|
|
171
|
+
BlocWithTitle,
|
|
172
|
+
DatasetsListBloc,
|
|
173
|
+
DataservicesListBloc,
|
|
174
|
+
ReusesListBloc,
|
|
175
|
+
LinkInBloc,
|
|
176
|
+
LinksListBloc,
|
|
177
|
+
MarkdownBloc,
|
|
178
|
+
AccordionItemBloc,
|
|
179
|
+
AccordionListBloc,
|
|
180
|
+
HeroBloc,
|
|
135
181
|
PaginatedArray,
|
|
182
|
+
Post,
|
|
183
|
+
Thread,
|
|
136
184
|
Quality,
|
|
137
185
|
RegisteredSchema,
|
|
138
186
|
Rel,
|
|
@@ -142,7 +190,9 @@ export type {
|
|
|
142
190
|
ReportReason,
|
|
143
191
|
Resource,
|
|
144
192
|
ResourceFileType,
|
|
193
|
+
ResourceGroup,
|
|
145
194
|
ResourceType,
|
|
195
|
+
ReuseReference,
|
|
146
196
|
Reuse,
|
|
147
197
|
ReuseTopic,
|
|
148
198
|
ReuseType,
|
|
@@ -167,6 +217,16 @@ export type {
|
|
|
167
217
|
WellType,
|
|
168
218
|
}
|
|
169
219
|
|
|
220
|
+
export {
|
|
221
|
+
getDefaultDatasetConfig,
|
|
222
|
+
getDefaultDataserviceConfig,
|
|
223
|
+
getDefaultReuseConfig,
|
|
224
|
+
getDefaultGlobalSearchConfig,
|
|
225
|
+
defaultDatasetSortOptions,
|
|
226
|
+
defaultDataserviceSortOptions,
|
|
227
|
+
defaultReuseSortOptions,
|
|
228
|
+
}
|
|
229
|
+
|
|
170
230
|
// Vue Plugin
|
|
171
231
|
const datagouv: Plugin<PluginConfig> = {
|
|
172
232
|
async install(app: App, options) {
|
|
@@ -191,7 +251,14 @@ export {
|
|
|
191
251
|
CopyButton,
|
|
192
252
|
DataserviceCard,
|
|
193
253
|
DatasetCard,
|
|
194
|
-
|
|
254
|
+
DatasetInformationSection,
|
|
255
|
+
DatasetTemporalitySection,
|
|
256
|
+
DatasetSpatialSection,
|
|
257
|
+
DatasetSchemaSection,
|
|
258
|
+
DatasetEmbedSection,
|
|
259
|
+
DescriptionListTerm,
|
|
260
|
+
DescriptionListDetails,
|
|
261
|
+
DiscussionMessageCard,
|
|
195
262
|
DatasetQuality,
|
|
196
263
|
DatasetQualityInline,
|
|
197
264
|
DatasetQualityItem,
|
|
@@ -200,7 +267,10 @@ export {
|
|
|
200
267
|
DateRangeDetails,
|
|
201
268
|
ExtraAccordion,
|
|
202
269
|
LabelTag,
|
|
270
|
+
LeafletMap,
|
|
271
|
+
LicenseBadge,
|
|
203
272
|
LoadingBlock,
|
|
273
|
+
Tag,
|
|
204
274
|
MarkdownViewer,
|
|
205
275
|
OrganizationCard,
|
|
206
276
|
OrganizationLogo,
|
|
@@ -210,11 +280,18 @@ export {
|
|
|
210
280
|
PaddedContainer,
|
|
211
281
|
Pagination,
|
|
212
282
|
Placeholder,
|
|
283
|
+
PostCard,
|
|
284
|
+
RadioGroup,
|
|
285
|
+
RadioInput,
|
|
213
286
|
ReadMore,
|
|
214
287
|
ResourceAccordion,
|
|
288
|
+
ResourceExplorer,
|
|
289
|
+
ResourceExplorerSidebar,
|
|
290
|
+
ResourceExplorerViewer,
|
|
215
291
|
ResourceIcon,
|
|
216
292
|
ReuseCard,
|
|
217
293
|
ReuseDetails,
|
|
294
|
+
ReuseHorizontalCard,
|
|
218
295
|
SchemaCard,
|
|
219
296
|
SimpleBanner,
|
|
220
297
|
SmallChart,
|
|
@@ -227,6 +304,11 @@ export {
|
|
|
227
304
|
TabPanels,
|
|
228
305
|
Tooltip,
|
|
229
306
|
Toggletip,
|
|
307
|
+
TopicCard,
|
|
230
308
|
TranslationT,
|
|
231
309
|
UserActivityList,
|
|
310
|
+
GlobalSearch,
|
|
311
|
+
SearchInput,
|
|
312
|
+
SearchableSelect,
|
|
313
|
+
SelectGroup,
|
|
232
314
|
}
|
|
@@ -2,19 +2,21 @@ import type { Harvest } from './harvest'
|
|
|
2
2
|
import type { Owned, OwnedWithId } from './owned'
|
|
3
3
|
import type { ContactPoint } from './contact_point'
|
|
4
4
|
import type { WithAccessType } from './access_types'
|
|
5
|
+
import type { DatasetReference } from './datasets'
|
|
6
|
+
|
|
7
|
+
export type DataserviceReference = {
|
|
8
|
+
class: 'Dataservice'
|
|
9
|
+
id: string
|
|
10
|
+
title: string
|
|
11
|
+
self_api_url: string
|
|
12
|
+
self_web_url: string
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
export type BaseDataservice = Owned & WithAccessType & {
|
|
7
16
|
acronym: string
|
|
8
17
|
availability: number | null
|
|
9
18
|
base_api_url: string | null
|
|
10
|
-
datasets: Array<
|
|
11
|
-
class: string
|
|
12
|
-
id: string
|
|
13
|
-
acronym: string
|
|
14
|
-
page: string
|
|
15
|
-
title: string
|
|
16
|
-
uri: string
|
|
17
|
-
}>
|
|
19
|
+
datasets: Array<DatasetReference>
|
|
18
20
|
description: string
|
|
19
21
|
machine_documentation_url: string | null
|
|
20
22
|
technical_documentation_url: string | null
|
|
@@ -22,7 +24,7 @@ export type BaseDataservice = Owned & WithAccessType & {
|
|
|
22
24
|
license: string | null
|
|
23
25
|
private: boolean
|
|
24
26
|
rate_limiting: string
|
|
25
|
-
title:
|
|
27
|
+
title: DataserviceReference['title']
|
|
26
28
|
contact_points: Array<ContactPoint>
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -50,7 +52,7 @@ export type Dataservice = Owned & WithAccessType & {
|
|
|
50
52
|
extras: Record<string, unknown>
|
|
51
53
|
format: string
|
|
52
54
|
harvest: Harvest
|
|
53
|
-
id:
|
|
55
|
+
id: DataserviceReference['id']
|
|
54
56
|
license: string | null
|
|
55
57
|
metadata_modified_at: string
|
|
56
58
|
metrics: {
|
|
@@ -63,8 +65,8 @@ export type Dataservice = Owned & WithAccessType & {
|
|
|
63
65
|
permissions: { edit: boolean, delete: boolean }
|
|
64
66
|
private: boolean
|
|
65
67
|
rate_limiting: string
|
|
66
|
-
self_api_url:
|
|
67
|
-
self_web_url:
|
|
68
|
+
self_api_url: DataserviceReference['self_api_url']
|
|
69
|
+
self_web_url: DataserviceReference['self_web_url']
|
|
68
70
|
slug: string
|
|
69
71
|
tags: Array<string>
|
|
70
72
|
title: string
|