@datagouv/components-next 0.0.29 → 0.0.31

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 (45) hide show
  1. package/README.md +10 -2
  2. package/assets/main.css +4 -0
  3. package/dist/{JsonPreview.client-Dv-dKju6.js → JsonPreview.client-B5cv59th.js} +12 -12
  4. package/dist/{MapContainer.client-Di5UOWc5.js → MapContainer.client-DXVDyZYz.js} +1 -1
  5. package/dist/{PdfPreview.client-BPzufzxb.js → PdfPreview.client-XwjUHnmx.js} +5 -5
  6. package/dist/{Pmtiles.client-0o7LzYmY.js → Pmtiles.client-DfnKDlpg.js} +1 -1
  7. package/dist/Swagger.client-BisHyZkP.js +4 -0
  8. package/dist/{XmlPreview.client-BwkMlaG7.js → XmlPreview.client-BSjMew4d.js} +14 -14
  9. package/dist/components-next.css +1 -1
  10. package/dist/components-next.js +32 -31
  11. package/dist/components.css +1 -1
  12. package/dist/{main-DIQDGjO4.js → main-Qu3kUOIH.js} +14488 -14473
  13. package/dist/{vue3-xml-viewer.common-CpVTMgj2.js → vue3-xml-viewer.common-NQY1dx9T.js} +1 -1
  14. package/package.json +1 -1
  15. package/src/components/Avatar.vue +2 -1
  16. package/src/components/BrandedButton.vue +4 -1
  17. package/src/components/ClientOnly.vue +17 -0
  18. package/src/components/DataserviceCard.vue +50 -78
  19. package/src/components/DatasetCard.vue +14 -14
  20. package/src/components/DatasetQuality.vue +1 -2
  21. package/src/components/DatasetQualityInline.vue +1 -3
  22. package/src/components/OrganizationCard.vue +9 -7
  23. package/src/components/OwnerType.vue +1 -1
  24. package/src/components/ResourceAccordion/DataStructure.vue +6 -5
  25. package/src/components/ResourceAccordion/JsonPreview.client.vue +5 -5
  26. package/src/components/ResourceAccordion/PdfPreview.client.vue +3 -3
  27. package/src/components/ResourceAccordion/Preview.vue +5 -5
  28. package/src/components/ResourceAccordion/ResourceAccordion.vue +2 -2
  29. package/src/components/ResourceAccordion/SchemaBadge.vue +5 -3
  30. package/src/components/ResourceAccordion/XmlPreview.client.vue +5 -5
  31. package/src/components/Tabs/TabGroup.vue +13 -1
  32. package/src/components/Toggletip.vue +81 -49
  33. package/src/components/ValueWatcher.vue +18 -0
  34. package/src/composables/useReuseType.ts +2 -1
  35. package/src/config.ts +5 -4
  36. package/src/functions/organizations.ts +14 -0
  37. package/src/functions/reuses.ts +10 -7
  38. package/src/functions/schemas.ts +32 -31
  39. package/src/functions/tabularApi.ts +2 -3
  40. package/src/functions/users.ts +2 -2
  41. package/src/main.ts +2 -0
  42. package/src/types/dataservices.ts +1 -0
  43. package/src/types/site.ts +3 -0
  44. package/dist/Swagger.client-H6fmX_7L.js +0 -4
  45. package/src/components/ToggletipButton.vue +0 -14
@@ -1,62 +1,94 @@
1
1
  <template>
2
2
  <Popover
3
- v-slot="{ open }"
4
- class="relative text-gray-title"
5
- :focus="true"
3
+ v-slot="{ open, close }"
4
+ class="relative"
6
5
  >
7
- <PopoverButton
8
- v-bind="$attrs"
9
- ref="button"
10
- :as="ToggletipButton"
11
- >
12
- <slot />
13
- </PopoverButton>
14
- <component
15
- :is="teleportId ? Teleport : 'div'"
16
- v-if="open"
17
- :to="`#${teleportId}`"
18
- :defer="teleportId ? true : undefined"
19
- >
20
- <PopoverPanel
21
- ref="toggletip"
22
- v-slot="{ close }"
23
- class="toggletip"
24
- :class="{
25
- 'p-0': noMargin,
26
- 'left-0': position === 'right',
27
- 'ml-6 top-24': teleportId,
28
- }"
6
+ <!--
7
+ Little trick to watch for v-slot changes because HeadlessUI doesn't raise an event on open… :-(
8
+ Need to recompute on show because sometimes, the positions where incorrect because after first render, some div load and change the relative position of the button
9
+ -->
10
+ <ValueWatcher
11
+ :value="open"
12
+ @changed="calculatePanelPosition"
13
+ />
14
+ <PopoverButton ref="button">
15
+ <BrandedButton
16
+ color="secondary-softer"
17
+ icon-only
18
+ :icon="RiInformationLine"
19
+ size="xs"
20
+ keep-margins-even-without-borders
21
+ v-bind="buttonProps"
29
22
  >
30
- <slot
31
- name="toggletip"
32
- :close
33
- />
34
- </PopoverPanel>
35
- </component>
23
+ <slot />
24
+ </BrandedButton>
25
+ </PopoverButton>
26
+
27
+ <ClientOnly>
28
+ <Teleport to="#tooltips">
29
+ <PopoverPanel
30
+ v-show="open"
31
+ class="toggletip absolute z-10"
32
+ :class="{
33
+ 'p-0': noMargin,
34
+ }"
35
+ :style="panelStyle"
36
+ static
37
+ >
38
+ <slot
39
+ name="toggletip"
40
+ :close
41
+ />
42
+ </PopoverPanel>
43
+ </Teleport>
44
+ </ClientOnly>
36
45
  </Popover>
37
46
  </template>
38
47
 
39
48
  <script setup lang="ts">
40
49
  import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
41
- import { Teleport } from 'vue'
42
- import ToggletipButton from './ToggletipButton.vue'
50
+ import { nextTick, onBeforeUnmount, onMounted, onUpdated, ref, useTemplateRef } from 'vue'
51
+ import { RiInformationLine } from '@remixicon/vue'
52
+ import BrandedButton from './BrandedButton.vue'
53
+ import ClientOnly from './ClientOnly.vue'
54
+ import ValueWatcher from './ValueWatcher.vue'
43
55
 
44
- withDefaults(defineProps<{
56
+ defineProps<{
57
+ buttonProps?: object
45
58
  noMargin?: boolean
46
- position?: 'left' | 'right'
47
- teleportId?: string
48
- }>(), {
49
- noMargin: false,
50
- position: 'left',
59
+ }>()
60
+
61
+ const buttonRef = useTemplateRef('button')
62
+ const panelStyle = ref({})
63
+
64
+ // Since the parent of the component can have an overflow-hidden
65
+ // we teleport the popover to a #tooltips div in the layout.
66
+ // We need to compute the correct position of the tooltip.
67
+ const calculatePanelPosition = () => {
68
+ nextTick(() => {
69
+ const button = buttonRef.value?.$el || buttonRef.value
70
+
71
+ if (!button) {
72
+ console.error('Cannot find the button of the Toggletip.)')
73
+ return
74
+ }
75
+
76
+ const buttonRect = button.getBoundingClientRect()
77
+ panelStyle.value = {
78
+ left: `${buttonRect.left + window.scrollX}px`,
79
+ top: `${buttonRect.bottom + window.scrollY}px`,
80
+ }
81
+ })
82
+ }
83
+
84
+ onMounted(() => {
85
+ calculatePanelPosition()
86
+ window.addEventListener('resize', calculatePanelPosition)
51
87
  })
52
- defineOptions({ inheritAttrs: false })
53
- </script>
54
88
 
55
- <style scoped>
56
- .z-10 {
57
- z-index: 10;
58
- }
59
- .left-0 {
60
- left: 0;
61
- }
62
- </style>
89
+ onBeforeUnmount(() => {
90
+ window.removeEventListener('resize', calculatePanelPosition)
91
+ })
92
+
93
+ onUpdated(() => calculatePanelPosition())
94
+ </script>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <slot />
3
+ </template>
4
+
5
+ <script setup lang="ts" generic="T">
6
+ import { watch } from 'vue'
7
+
8
+ const props = defineProps<{
9
+ value: T
10
+ }>()
11
+ const emits = defineEmits<{
12
+ changed: [T]
13
+ }>()
14
+
15
+ watch(() => props.value, () => {
16
+ emits('changed', props.value)
17
+ })
18
+ </script>
@@ -1,8 +1,9 @@
1
1
  import { computedAsync } from '@vueuse/core'
2
2
  import { toValue, type MaybeRefOrGetter } from 'vue'
3
- import { fetchReuseTypes, getType } from '../functions/reuses'
3
+ import { useFetchReuseTypes, getType } from '../functions/reuses'
4
4
 
5
5
  export function useReuseType(id: MaybeRefOrGetter<string>) {
6
+ const fetchReuseTypes = useFetchReuseTypes()
6
7
  const label = computedAsync(async () => {
7
8
  const idValue = toValue(id)
8
9
  const types = await fetchReuseTypes()
package/src/config.ts CHANGED
@@ -7,19 +7,20 @@ export type PluginConfig = {
7
7
  apiBase: string
8
8
  devApiKey?: string | null
9
9
  datasetQualityGuideUrl?: string
10
+ maxJsonPreviewCharSize?: number // Maximum size of JSON to preview in characters. JSON preview module is partly collapsed by default so we can have a preview for large files.
11
+ maxPdfPreviewByteSize?: number // Maximum size of PDF to preview in bytes
12
+ maxXmlPreviewCharSize?: number // Maximum size of XML to preview in characters. XML preview module can NOT be collapsed by default so we should not have a preview for large files.
13
+ pmtilesViewerBaseUrl?: string | null // Base URL of a pmtiles viewer (ex: https://pmtiles.io/#url=)
10
14
  schemaValidataUrl?: string
11
15
  schemaDocumentationUrl?: string
12
16
  tabularApiUrl?: string
13
17
  tabularApiPageSize?: number
14
18
  tabularAllowRemote?: boolean
15
19
  tabularApiDataserviceId?: string
16
- pmtilesViewerBaseUrl?: string | null // Base URL of a pmtiles viewer (ex: https://pmtiles.io/#url=)
17
20
  customUseFetch?: UseFetchFunction | null
18
21
  textClamp?: string | Component | null
19
22
  appLink?: Component | null
20
- maxJsonPreviewSize?: number // Maximum size of JSON to preview in characters
21
- maxPdfPreviewSize?: number // Maximum size of PDF to preview in bytes
22
- maxXmlPreviewSize?: number // Maximum size of XML to preview in characters
23
+ clientOnly?: Component | null
23
24
  }
24
25
 
25
26
  export const configKey = Symbol() as InjectionKey<PluginConfig>
@@ -1,6 +1,7 @@
1
1
  import { useI18n } from 'vue-i18n'
2
2
  import type { Component } from 'vue'
3
3
  import { RiBankLine, RiBuilding2Line, RiCommunityLine, RiGovernmentLine, RiUserLine } from '@remixicon/vue'
4
+ import { useComponentsConfig } from '../config'
4
5
  import type { Organization } from '../types/organizations'
5
6
 
6
7
  export const CERTIFIED = 'certified'
@@ -15,6 +16,12 @@ export type OrganizationTypes = typeof PUBLIC_SERVICE | typeof ASSOCIATION | typ
15
16
 
16
17
  export type UserType = typeof USER
17
18
 
19
+ function constructUrl(baseUrl: string, path: string): string {
20
+ const url = new URL(baseUrl)
21
+ url.pathname = `${url.pathname}${path}`
22
+ return url.toString()
23
+ }
24
+
18
25
  export function isType(organization: Organization, type: OrganizationTypes) {
19
26
  return hasBadge(organization, type)
20
27
  }
@@ -83,3 +90,10 @@ export function isOrganizationCertified(organization: Organization | null): bool
83
90
  if (!organization) return false
84
91
  return hasBadge(organization, CERTIFIED) && (isType(organization, PUBLIC_SERVICE) || isType(organization, LOCAL_AUTHORITY))
85
92
  }
93
+
94
+ export default function getOrganizationOEmbedHtml(type: string, id: string): string {
95
+ const config = useComponentsConfig()
96
+
97
+ const staticUrl = constructUrl(config.baseUrl, 'oembed.js')
98
+ return `<div data-udata-${type}="${id}" data-height="1500" data-width="1200"></div><script data-udata="${config.baseUrl}" src="${staticUrl}" async defer></script>`
99
+ }
@@ -4,17 +4,20 @@ import { useComponentsConfig } from '../config'
4
4
  import type { ReuseTopic, ReuseType } from '../types/reuses'
5
5
 
6
6
  let reuseTypesRequest: Promise<Array<ReuseType>> | null = null
7
+ export function useFetchReuseTypes() {
8
+ const config = useComponentsConfig()
9
+ const { locale } = useI18n()
7
10
 
8
- export async function fetchReuseTypes() {
9
- if (!reuseTypesRequest) {
10
- const config = useComponentsConfig()
11
- const { locale } = useI18n()
12
- reuseTypesRequest = ofetch<Array<ReuseType>>('api/1/reuses/types/', {
11
+ return async (): Promise<Array<ReuseType>> => {
12
+ if (reuseTypesRequest) {
13
+ return reuseTypesRequest
14
+ }
15
+
16
+ return await (reuseTypesRequest = ofetch<Array<ReuseType>>('api/1/reuses/types/', {
13
17
  baseURL: config.apiBase,
14
18
  query: { lang: locale.value },
15
- })
19
+ }))
16
20
  }
17
- return await reuseTypesRequest
18
21
  }
19
22
 
20
23
  export function getType(types: Array<ReuseType>, id: string): string {
@@ -45,20 +45,19 @@ export type SchemaResponseData = Array<RegisteredSchema>
45
45
 
46
46
  type SchemaPath = { schema_name: string } | { schema_url: string }
47
47
 
48
- const catalogRequest: Promise<Array<RegisteredSchema>> | null = null
49
-
50
- /**
51
- * Get Schema Catalog
52
- */
53
- export async function getCatalog(): Promise<SchemaResponseData> {
48
+ let catalogRequest: Promise<Array<RegisteredSchema>> | null = null
49
+ export function useGetCatalog() {
54
50
  const config = useComponentsConfig()
55
- if (catalogRequest) {
56
- return catalogRequest
57
- }
58
51
 
59
- return await ofetch('api/1/datasets/schemas/', {
60
- baseURL: config.apiBase,
61
- })
52
+ return async (): Promise<SchemaResponseData> => {
53
+ if (catalogRequest) {
54
+ return catalogRequest
55
+ }
56
+
57
+ return await (catalogRequest = ofetch('api/1/datasets/schemas/', {
58
+ baseURL: config.apiBase,
59
+ }))
60
+ }
62
61
  }
63
62
 
64
63
  export function findSchemaInCatalog(catalog: Array<RegisteredSchema>, schema: Schema | null): RegisteredSchema | null {
@@ -66,30 +65,32 @@ export function findSchemaInCatalog(catalog: Array<RegisteredSchema>, schema: Sc
66
65
  return catalog.find(registeredSchema => schema.name === registeredSchema.name) || null
67
66
  }
68
67
 
69
- export function getSchemaDocumentation(name: string): string {
68
+ export function useGetSchemaDocumentation() {
70
69
  const config = useComponentsConfig()
71
- return `${config.schemaDocumentationUrl}${name}/`
70
+ return (name: string) => `${config.schemaDocumentationUrl}${name}/`
72
71
  }
73
72
 
74
- export function getSchemaValidationUrl(resource: Resource, registeredSchema: RegisteredSchema): string | null {
73
+ export function useGetSchemaValidationUrl() {
75
74
  const config = useComponentsConfig()
76
- if (!resource.schema || !resource.schema.name) {
77
- return null
78
- }
75
+ return (resource: Resource, registeredSchema: RegisteredSchema) => {
76
+ if (!resource.schema || !resource.schema.name) {
77
+ return null
78
+ }
79
79
 
80
- let schemaPath: SchemaPath = { schema_name: `schema-datagouvfr.${resource.schema.name}` }
81
- if (resource.schema && resource.schema.version) {
82
- const schemaVersion = resource.schema.version
83
- const versionUrl = registeredSchema.versions.find(version => version.version_name === schemaVersion)?.schema_url
84
- if (versionUrl) {
85
- schemaPath = { schema_url: versionUrl }
80
+ let schemaPath: SchemaPath = { schema_name: `schema-datagouvfr.${resource.schema.name}` }
81
+ if (resource.schema && resource.schema.version) {
82
+ const schemaVersion = resource.schema.version
83
+ const versionUrl = registeredSchema.versions.find(version => version.version_name === schemaVersion)?.schema_url
84
+ if (versionUrl) {
85
+ schemaPath = { schema_url: versionUrl }
86
+ }
86
87
  }
88
+ const query = new URLSearchParams({
89
+ 'input': 'url',
90
+ 'url': resource.url,
91
+ 'header-case': 'on',
92
+ ...schemaPath,
93
+ }).toString()
94
+ return `${config.schemaValidataUrl}/table-schema?${query}`
87
95
  }
88
- const query = new URLSearchParams({
89
- 'input': 'url',
90
- 'url': resource.url,
91
- 'header-case': 'on',
92
- ...schemaPath,
93
- }).toString()
94
- return `${config.schemaValidataUrl}/table-schema?${query}`
95
96
  }
@@ -20,8 +20,7 @@ export async function getData(config: PluginConfig, id: string, page: number, so
20
20
  /**
21
21
  * Call Tabular-api to get table profile
22
22
  */
23
- export async function getProfile(id: string) {
23
+ export function useGetProfile() {
24
24
  const config = useComponentsConfig()
25
-
26
- return await ofetch(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
25
+ return (id: string) => ofetch(`${config.tabularApiUrl}/api/resources/${id}/profile/`)
27
26
  }
@@ -1,7 +1,7 @@
1
1
  import type { User } from '../types/users'
2
2
  import { useComponentsConfig } from '../config'
3
3
 
4
- export function getUserAvatar(user: User, size: number) {
4
+ export function useGetUserAvatar() {
5
5
  const config = useComponentsConfig()
6
- return user.avatar_thumbnail || `${config.apiBase}/api/1/avatars/${user.id}/${size}`
6
+ return (user: User, size: number) => user.avatar_thumbnail || `${config.apiBase}/api/1/avatars/${user.id}/${size}`
7
7
  }
package/src/main.ts CHANGED
@@ -47,6 +47,7 @@ import ReuseCard from './components/ReuseCard.vue'
47
47
  import SimpleBanner from './components/SimpleBanner.vue'
48
48
  import StatBox from './components/StatBox.vue'
49
49
  import Tooltip from './components/Tooltip.vue'
50
+ import Toggletip from './components/Toggletip.vue'
50
51
  import type { UseFetchFunction } from './functions/api.types'
51
52
  import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
52
53
 
@@ -172,4 +173,5 @@ export {
172
173
  StatBox,
173
174
  Swagger,
174
175
  Tooltip,
176
+ Toggletip,
175
177
  }
@@ -53,6 +53,7 @@ export type Dataservice = Owned & {
53
53
  }>
54
54
  deleted_at: string | null
55
55
  description: string
56
+ featured: boolean
56
57
  machine_documentation_url: string | null
57
58
  technical_documentation_url: string | null
58
59
  business_documentation_url: string | null
package/src/types/site.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export type Site = {
2
2
  id: string
3
3
  title: string
4
+ datasets_page: string | null
5
+ reuses_page: string | null
6
+ dataservices_page: string | null
4
7
  metrics: {
5
8
  'dataservices': number
6
9
  'datasets': number
@@ -1,4 +0,0 @@
1
- import { _ as f } from "./main-DIQDGjO4.js";
2
- export {
3
- f as default
4
- };
@@ -1,14 +0,0 @@
1
- <template>
2
- <BrandedButton
3
- color="secondary-softer"
4
- icon-only
5
- :icon="RiInformationLine"
6
- size="xs"
7
- keep-margins-even-without-borders
8
- />
9
- </template>
10
-
11
- <script setup lang="ts">
12
- import { RiInformationLine } from '@remixicon/vue'
13
- import BrandedButton from './BrandedButton.vue'
14
- </script>