@datagouv/components-next 1.0.2-dev.2 → 1.0.2-dev.200
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 +7 -6
- package/assets/main.css +4 -0
- package/dist/{Control-DuZJdKV_.js → Control-B9WQxlpJ.js} +1 -1
- package/dist/Datafair.client-CFTmBFoY.js +30 -0
- package/dist/{Event--kp8kMdJ.js → Event-iXDrp-fy.js} +31 -31
- package/dist/{Image-34hvypZI.js → Image-mNMBOofs.js} +9 -9
- package/dist/ImagePreview.client-BvDM3qjE.js +36 -0
- package/dist/JsonPreview.client-BP6gPRxf.js +40 -0
- package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
- package/dist/MapContainer.client-D9N3664r.js +101 -0
- package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
- package/dist/{PdfPreview.client-C-w6-w44.js → PdfPreview.client-C6A6ZiKl.js} +835 -871
- package/dist/Pmtiles.client-B_sDZoqu.js +26333 -0
- package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-DGLXBys_.js +60 -0
- package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-VGQ7nkMS.js} +2 -2
- package/dist/{Tile-DbNFNPfU.js → Tile-CSpn0Ca8.js} +160 -163
- package/dist/{TileImage-BsXBxMtq.js → TileImage-CpbaEFwd.js} +143 -131
- package/dist/{View-BR92hTWP.js → View-BYBpt-AU.js} +483 -422
- package/dist/XmlPreview.client-DFQ1T-LN.js +34 -0
- package/dist/{common-PJfpC179.js → common-BoeTUnDZ.js} +52 -52
- package/dist/components-next.css +1 -52
- package/dist/components-next.js +196 -150
- package/dist/components.css +2 -2
- package/dist/{index-SrYZwgCT.js → index-B_tPYi5Z.js} +34094 -27864
- package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-PeylHk0o.js} +162 -164
- package/dist/main-CVOZp_Nx.js +75398 -0
- package/dist/{proj-DsetBcW7.js → proj-DxGbobp7.js} +662 -626
- package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
- package/dist/{tilecoord-Db24Px13.js → tilecoord-DvX7WWiA.js} +273 -264
- package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
- package/dist/{vue3-xml-viewer.common-BRxsqI9j.js → vue3-xml-viewer.common-CcF3Xd23.js} +1 -1
- package/package.json +50 -37
- package/src/chart.ts +5 -0
- package/src/components/ActivityList/ActivityList.vue +24 -14
- package/src/components/AppLink.vue +3 -0
- package/src/components/BannerAction.vue +6 -2
- package/src/components/Chart/ChartViewer.vue +320 -0
- package/src/components/Chart/ChartViewerWrapper.vue +182 -0
- package/src/components/CopyButton.vue +6 -2
- package/src/components/DataserviceCard.vue +24 -5
- package/src/components/DataserviceQuality.vue +82 -0
- package/src/components/DataserviceQualityInline.vue +31 -0
- package/src/components/DataserviceQualityTooltipContent.vue +79 -0
- package/src/components/DatasetCard.vue +24 -9
- package/src/components/DatasetInformation/DatasetTemporalitySection.vue +7 -4
- package/src/components/DatasetQuality.vue +11 -11
- package/src/components/DatasetQualityInline.vue +2 -2
- package/src/components/DatasetQualityTooltipContent.vue +1 -1
- package/src/components/DiscussionMessageCard.vue +14 -15
- package/src/components/Form/Listbox.vue +101 -0
- package/src/components/Form/SchemaSelect.vue +2 -0
- package/src/components/Form/SearchableSelect.vue +2 -1
- package/src/components/FormattedDate.vue +81 -0
- package/src/components/InfiniteLoader.vue +53 -0
- package/src/components/LoadingBlock.vue +4 -1
- package/src/components/ObjectCardHeader.vue +11 -4
- package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
- package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
- package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
- package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
- package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
- package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
- package/src/components/OpenApiViewer/openapi.ts +150 -0
- package/src/components/OrganizationNameWithCertificate.vue +3 -2
- package/src/components/Pagination.vue +25 -62
- package/src/components/PostCard.vue +13 -15
- package/src/components/RadioInput.vue +7 -2
- package/src/components/ReadMore.vue +1 -1
- package/src/components/ResourceAccordion/DataStructure.vue +11 -33
- package/src/components/ResourceAccordion/Datafair.client.vue +4 -10
- package/src/components/ResourceAccordion/Downloads.vue +172 -0
- package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
- package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -121
- package/src/components/ResourceAccordion/MapContainer.client.vue +6 -19
- package/src/components/ResourceAccordion/Metadata.vue +4 -6
- package/src/components/ResourceAccordion/PdfPreview.client.vue +58 -113
- package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
- package/src/components/ResourceAccordion/Pmtiles.client.vue +33 -23
- package/src/components/ResourceAccordion/Preview.vue +24 -25
- package/src/components/ResourceAccordion/PreviewLoader.vue +1 -2
- package/src/components/ResourceAccordion/PreviewUnavailable.vue +22 -0
- package/src/components/ResourceAccordion/PreviewWrapper.vue +89 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +36 -117
- package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -115
- package/src/components/ResourceExplorer/ResourceExplorer.vue +105 -199
- package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +79 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +104 -71
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +143 -280
- package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
- package/src/components/ResourceExplorer/ResourceSelector.vue +51 -0
- package/src/components/ResourceExplorer/ResourceViewerHeader.vue +122 -0
- package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +94 -0
- package/src/components/ResourceListItem.vue +168 -0
- package/src/components/ReuseCard.vue +28 -9
- package/src/components/ReuseDetails.vue +2 -2
- package/src/components/ReuseHorizontalCard.vue +2 -2
- package/src/components/SchemaCard.vue +10 -1
- package/src/components/Search/Filter/AccessTypeFilter.vue +2 -2
- package/src/components/Search/GlobalSearch.vue +229 -123
- package/src/components/Search/SearchInput.vue +5 -4
- package/src/components/Search/Sidemenu.vue +4 -3
- package/src/components/SmallChart.vue +11 -10
- package/src/components/StatBox.vue +13 -5
- package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
- package/src/components/TabularExplorer/TabularCell.vue +50 -0
- package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
- package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
- package/src/components/TabularExplorer/TabularExplorer.vue +299 -0
- package/src/components/TabularExplorer/TabularFilterContent.vue +362 -0
- package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
- package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
- package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
- package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
- package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
- package/src/components/TabularExplorer/TabularTable.vue +481 -0
- package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
- package/src/components/TabularExplorer/types.ts +84 -0
- package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
- package/src/components/TabularExplorer/useTabularContext.ts +82 -0
- package/src/components/TopicCard.vue +13 -14
- package/src/composables/useActiveDescendant.ts +5 -0
- package/src/composables/useDatasetResources.ts +164 -0
- package/src/composables/useHasTabularData.ts +15 -1
- package/src/composables/useMetrics.ts +1 -1
- package/src/composables/useResizable.ts +46 -0
- package/src/composables/useResourceCapabilities.ts +49 -41
- package/src/composables/useSearchFilter.ts +120 -0
- package/src/composables/useStableQueryParams.ts +38 -6
- package/src/composables/useTabularProfile.ts +70 -0
- package/src/composables/useTranslation.ts +24 -9
- package/src/config.ts +21 -3
- package/src/functions/activities.ts +3 -3
- package/src/functions/api.ts +20 -42
- package/src/functions/api.types.ts +1 -0
- package/src/functions/charts.ts +99 -0
- package/src/functions/datasets.ts +0 -17
- package/src/functions/dates.ts +6 -7
- package/src/functions/helpers.ts +15 -0
- package/src/functions/metrics.ts +63 -60
- package/src/functions/paginate.ts +46 -0
- package/src/functions/resourceCapabilities.ts +34 -0
- package/src/functions/resources.ts +136 -1
- package/src/functions/schemas.ts +27 -0
- package/src/functions/scroll.ts +20 -0
- package/src/functions/tabular.ts +214 -0
- package/src/functions/tabularApi.ts +96 -11
- package/src/main.ts +119 -14
- package/src/types/dataservices.ts +17 -0
- package/src/types/organizations.ts +11 -1
- package/src/types/pages.ts +0 -5
- package/src/types/posts.ts +2 -2
- package/src/types/reports.ts +5 -1
- package/src/types/resources.ts +0 -1
- package/src/types/reuses.ts +12 -1
- package/src/types/search.ts +63 -1
- package/src/types/site.ts +5 -3
- package/src/types/ui.ts +2 -0
- package/src/types/users.ts +2 -1
- package/src/types/visualizations.ts +93 -0
- package/assets/swagger-themes/newspaper.css +0 -1670
- package/dist/Datafair.client-E5D6ePRC.js +0 -35
- package/dist/JsonPreview.client-C-6eBbPw.js +0 -87
- package/dist/MapContainer.client-DRkAmdOc.js +0 -105
- package/dist/Pmtiles.client-BR7_ldHY.js +0 -25190
- package/dist/Swagger.client-D4-F6yEf.js +0 -4
- package/dist/XmlPreview.client-Dl2VCgXF.js +0 -79
- package/dist/main-B2kXxWRG.js +0 -105833
- package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
- package/src/functions/pagination.ts +0 -9
- /package/assets/illustrations/{_microscope.svg → microscope.svg} +0 -0
- /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
- /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
- /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component
|
|
3
|
+
:is="titleTag"
|
|
4
|
+
class="w-full text-base font-bold flex"
|
|
5
|
+
>
|
|
3
6
|
<AppLink
|
|
4
7
|
:to="url"
|
|
5
8
|
class="text-gray-title text-base bg-none flex items-center w-full truncate gap-1"
|
|
@@ -19,17 +22,21 @@
|
|
|
19
22
|
<slot name="extra" />
|
|
20
23
|
<span class="absolute inset-0" />
|
|
21
24
|
</AppLink>
|
|
22
|
-
</
|
|
25
|
+
</component>
|
|
23
26
|
</template>
|
|
24
27
|
|
|
25
28
|
<script setup lang="ts">
|
|
26
29
|
import type { Component } from 'vue'
|
|
27
30
|
import type { RouteLocationRaw } from 'vue-router'
|
|
31
|
+
import type { TitleTag } from '../types/ui'
|
|
28
32
|
import AppLink from './AppLink.vue'
|
|
29
33
|
|
|
30
|
-
defineProps<{
|
|
34
|
+
withDefaults(defineProps<{
|
|
31
35
|
icon: Component
|
|
32
36
|
url: RouteLocationRaw
|
|
33
37
|
target?: string
|
|
34
|
-
|
|
38
|
+
titleTag?: TitleTag
|
|
39
|
+
}>(), {
|
|
40
|
+
titleTag: 'h3',
|
|
41
|
+
})
|
|
35
42
|
</script>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Tooltip v-if="contentTypes.length > 1">
|
|
3
|
+
<div class="relative shrink-0">
|
|
4
|
+
<select
|
|
5
|
+
:value="modelValue"
|
|
6
|
+
class="appearance-none text-xs font-mono bg-white border border-gray-default rounded pl-2 pr-6 py-1 text-gray-medium cursor-pointer hover:border-gray-400 transition-colors"
|
|
7
|
+
@change="$emit('update:modelValue', ($event.target as HTMLSelectElement).value)"
|
|
8
|
+
>
|
|
9
|
+
<option
|
|
10
|
+
v-for="ct in contentTypes"
|
|
11
|
+
:key="ct"
|
|
12
|
+
:value="ct"
|
|
13
|
+
>
|
|
14
|
+
{{ contentTypeLabel(ct) }}
|
|
15
|
+
</option>
|
|
16
|
+
</select>
|
|
17
|
+
<RiArrowDownSLine class="pointer-events-none absolute right-1 top-1/2 -translate-y-1/2 size-3.5 text-gray-medium" />
|
|
18
|
+
</div>
|
|
19
|
+
<template #tooltip>
|
|
20
|
+
{{ modelValue }}
|
|
21
|
+
</template>
|
|
22
|
+
</Tooltip>
|
|
23
|
+
<Tooltip v-else-if="contentTypes.length === 1">
|
|
24
|
+
<span
|
|
25
|
+
class="text-xs font-mono bg-white border border-gray-default rounded px-2 py-1 text-gray-medium shrink-0"
|
|
26
|
+
>
|
|
27
|
+
{{ contentTypeLabel(contentTypes[0]!) }}
|
|
28
|
+
</span>
|
|
29
|
+
<template #tooltip>
|
|
30
|
+
{{ contentTypes[0] }}
|
|
31
|
+
</template>
|
|
32
|
+
</Tooltip>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script setup lang="ts">
|
|
36
|
+
import { RiArrowDownSLine } from '@remixicon/vue'
|
|
37
|
+
import Tooltip from '../Tooltip.vue'
|
|
38
|
+
import { contentTypeLabel } from './openapi'
|
|
39
|
+
|
|
40
|
+
defineProps<{
|
|
41
|
+
contentTypes: string[]
|
|
42
|
+
modelValue: string
|
|
43
|
+
}>()
|
|
44
|
+
|
|
45
|
+
defineEmits<{
|
|
46
|
+
'update:modelValue': [value: string]
|
|
47
|
+
}>()
|
|
48
|
+
</script>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="border border-gray-default rounded">
|
|
3
|
+
<div
|
|
4
|
+
v-if="!tabs.length"
|
|
5
|
+
class="p-3 text-xs text-gray-medium"
|
|
6
|
+
>
|
|
7
|
+
{{ t("Aucun paramètre de requête") }}
|
|
8
|
+
</div>
|
|
9
|
+
<TabGroup
|
|
10
|
+
v-else
|
|
11
|
+
size="sm"
|
|
12
|
+
@change="onTabChange"
|
|
13
|
+
>
|
|
14
|
+
<div class="bg-gray-100 px-3 py-2 border-b border-gray-default flex items-center justify-between gap-2">
|
|
15
|
+
<TabList>
|
|
16
|
+
<Tab
|
|
17
|
+
v-for="tab in tabs"
|
|
18
|
+
:key="tab.key"
|
|
19
|
+
>
|
|
20
|
+
{{ tab.label }}
|
|
21
|
+
</Tab>
|
|
22
|
+
</TabList>
|
|
23
|
+
<ContentTypeSelect
|
|
24
|
+
v-if="activeTab === 'body' && bodyContentTypes.length"
|
|
25
|
+
:content-types="bodyContentTypes"
|
|
26
|
+
:model-value="selectedBodyContentType"
|
|
27
|
+
@update:model-value="selectedBodyContentType = $event"
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
<TabPanels>
|
|
31
|
+
<TabPanel
|
|
32
|
+
v-for="tab in tabs"
|
|
33
|
+
:key="tab.key"
|
|
34
|
+
>
|
|
35
|
+
<div
|
|
36
|
+
v-if="tab.key === 'query'"
|
|
37
|
+
class="p-3"
|
|
38
|
+
>
|
|
39
|
+
<div class="space-y-0 divide-y divide-gray-100">
|
|
40
|
+
<div
|
|
41
|
+
v-for="param in queryParams"
|
|
42
|
+
:key="param.name"
|
|
43
|
+
class="py-2"
|
|
44
|
+
>
|
|
45
|
+
<div class="flex items-baseline gap-2">
|
|
46
|
+
<span class="font-mono text-xs text-gray-title">
|
|
47
|
+
{{ param.name }}
|
|
48
|
+
<span
|
|
49
|
+
v-if="param.required"
|
|
50
|
+
class="text-red-600"
|
|
51
|
+
>*</span>
|
|
52
|
+
</span>
|
|
53
|
+
<span class="font-mono text-xs text-gray-medium">{{ getSchemaType(endpoint.spec, param.schema) }}</span>
|
|
54
|
+
</div>
|
|
55
|
+
<p
|
|
56
|
+
v-if="param.description"
|
|
57
|
+
class="text-xs text-gray-medium mt-0.5 mb-0"
|
|
58
|
+
>
|
|
59
|
+
{{ param.description }}
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div
|
|
65
|
+
v-if="tab.key === 'path'"
|
|
66
|
+
class="p-3"
|
|
67
|
+
>
|
|
68
|
+
<div class="space-y-0 divide-y divide-gray-100">
|
|
69
|
+
<div
|
|
70
|
+
v-for="param in pathParams"
|
|
71
|
+
:key="param.name"
|
|
72
|
+
class="py-2"
|
|
73
|
+
>
|
|
74
|
+
<div class="flex items-baseline gap-2">
|
|
75
|
+
<span class="font-mono text-xs text-gray-title">
|
|
76
|
+
{{ param.name }}
|
|
77
|
+
<span class="text-red-600">*</span>
|
|
78
|
+
</span>
|
|
79
|
+
<span class="font-mono text-xs text-gray-medium">{{ getSchemaType(endpoint.spec, param.schema) }}</span>
|
|
80
|
+
</div>
|
|
81
|
+
<p
|
|
82
|
+
v-if="param.description"
|
|
83
|
+
class="text-xs text-gray-medium mt-0.5 mb-0"
|
|
84
|
+
>
|
|
85
|
+
{{ param.description }}
|
|
86
|
+
</p>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div
|
|
91
|
+
v-if="tab.key === 'body'"
|
|
92
|
+
class="p-3"
|
|
93
|
+
>
|
|
94
|
+
<SchemaPanel
|
|
95
|
+
v-if="currentBodyMediaType?.schema"
|
|
96
|
+
:spec="endpoint.spec"
|
|
97
|
+
:schema="currentBodyMediaType.schema"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
</TabPanel>
|
|
101
|
+
</TabPanels>
|
|
102
|
+
</TabGroup>
|
|
103
|
+
</div>
|
|
104
|
+
</template>
|
|
105
|
+
|
|
106
|
+
<script setup lang="ts">
|
|
107
|
+
import { computed, ref, watch } from 'vue'
|
|
108
|
+
import TabGroup from '../Tabs/TabGroup.vue'
|
|
109
|
+
import TabList from '../Tabs/TabList.vue'
|
|
110
|
+
import Tab from '../Tabs/Tab.vue'
|
|
111
|
+
import TabPanels from '../Tabs/TabPanels.vue'
|
|
112
|
+
import TabPanel from '../Tabs/TabPanel.vue'
|
|
113
|
+
import ContentTypeSelect from './ContentTypeSelect.vue'
|
|
114
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
115
|
+
import SchemaPanel from './SchemaPanel.vue'
|
|
116
|
+
import { getSchemaType, type Endpoint } from './openapi'
|
|
117
|
+
|
|
118
|
+
const props = defineProps<{
|
|
119
|
+
endpoint: Endpoint
|
|
120
|
+
}>()
|
|
121
|
+
|
|
122
|
+
const { t } = useTranslation()
|
|
123
|
+
|
|
124
|
+
const queryParams = computed(() => props.endpoint.parameters.filter(p => p.in === 'query'))
|
|
125
|
+
const pathParams = computed(() => props.endpoint.parameters.filter(p => p.in === 'path'))
|
|
126
|
+
|
|
127
|
+
const tabs = computed(() => {
|
|
128
|
+
const result: { key: string, label: string }[] = []
|
|
129
|
+
if (pathParams.value.length) {
|
|
130
|
+
result.push({ key: 'path', label: t('Path') })
|
|
131
|
+
}
|
|
132
|
+
if (queryParams.value.length) {
|
|
133
|
+
result.push({ key: 'query', label: t('Query') })
|
|
134
|
+
}
|
|
135
|
+
if (props.endpoint.requestBody) {
|
|
136
|
+
result.push({ key: 'body', label: t('Body') })
|
|
137
|
+
}
|
|
138
|
+
return result
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const activeTabIndex = ref(0)
|
|
142
|
+
const activeTab = computed(() => tabs.value[activeTabIndex.value]?.key || '')
|
|
143
|
+
const selectedBodyContentType = ref('')
|
|
144
|
+
|
|
145
|
+
const bodyContentTypes = computed(() => {
|
|
146
|
+
if (!props.endpoint.requestBody?.content) return []
|
|
147
|
+
return Object.keys(props.endpoint.requestBody.content)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const currentBodyMediaType = computed(() => {
|
|
151
|
+
if (!props.endpoint.requestBody?.content) return null
|
|
152
|
+
const ct = selectedBodyContentType.value || bodyContentTypes.value[0]
|
|
153
|
+
if (!ct) return null
|
|
154
|
+
return props.endpoint.requestBody.content[ct] || null
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
watch(bodyContentTypes, (types) => {
|
|
158
|
+
selectedBodyContentType.value = types[0] || ''
|
|
159
|
+
}, { immediate: true })
|
|
160
|
+
|
|
161
|
+
function onTabChange(index: number) {
|
|
162
|
+
activeTabIndex.value = index
|
|
163
|
+
}
|
|
164
|
+
</script>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="border border-gray-default rounded">
|
|
3
|
+
<TabGroup
|
|
4
|
+
size="sm"
|
|
5
|
+
@change="onTabChange"
|
|
6
|
+
>
|
|
7
|
+
<div class="bg-gray-100 px-3 py-2 border-b border-gray-default flex items-center gap-4">
|
|
8
|
+
<div
|
|
9
|
+
ref="tabListContainer"
|
|
10
|
+
class="overflow-x-auto flex-1 min-w-0"
|
|
11
|
+
:class="{ 'scroll-fade': canScrollRight }"
|
|
12
|
+
@scroll="onScroll"
|
|
13
|
+
>
|
|
14
|
+
<TabList>
|
|
15
|
+
<Tab
|
|
16
|
+
v-for="tab in tabs"
|
|
17
|
+
:key="tab.code"
|
|
18
|
+
>
|
|
19
|
+
<span
|
|
20
|
+
class="inline-block w-2 h-2 rounded-full mr-1.5"
|
|
21
|
+
:class="statusDotColor(tab.code)"
|
|
22
|
+
/>
|
|
23
|
+
{{ tab.code }}
|
|
24
|
+
</Tab>
|
|
25
|
+
</TabList>
|
|
26
|
+
</div>
|
|
27
|
+
<ContentTypeSelect
|
|
28
|
+
v-if="currentContentTypes.length"
|
|
29
|
+
:content-types="currentContentTypes"
|
|
30
|
+
:model-value="selectedContentType"
|
|
31
|
+
@update:model-value="selectedContentType = $event"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
<TabPanels>
|
|
35
|
+
<TabPanel
|
|
36
|
+
v-for="tab in tabs"
|
|
37
|
+
:key="tab.code"
|
|
38
|
+
>
|
|
39
|
+
<div class="p-3 space-y-3">
|
|
40
|
+
<p
|
|
41
|
+
v-if="tab.response.description"
|
|
42
|
+
class="text-xs text-gray-medium mb-0 pb-3 border-b border-gray-100"
|
|
43
|
+
>
|
|
44
|
+
{{ tab.response.description }}
|
|
45
|
+
</p>
|
|
46
|
+
<template v-if="currentMediaType?.schema">
|
|
47
|
+
<SchemaPanel
|
|
48
|
+
:spec="spec"
|
|
49
|
+
:schema="currentMediaType.schema"
|
|
50
|
+
/>
|
|
51
|
+
</template>
|
|
52
|
+
<p
|
|
53
|
+
v-else-if="!tab.response.content"
|
|
54
|
+
class="text-xs text-gray-medium mb-0"
|
|
55
|
+
>
|
|
56
|
+
{{ t("Pas de contenu") }}
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
</TabPanel>
|
|
60
|
+
</TabPanels>
|
|
61
|
+
</TabGroup>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script setup lang="ts">
|
|
66
|
+
import { computed, ref, watch, useTemplateRef, onMounted, nextTick } from 'vue'
|
|
67
|
+
import TabGroup from '../Tabs/TabGroup.vue'
|
|
68
|
+
import TabList from '../Tabs/TabList.vue'
|
|
69
|
+
import Tab from '../Tabs/Tab.vue'
|
|
70
|
+
import TabPanels from '../Tabs/TabPanels.vue'
|
|
71
|
+
import TabPanel from '../Tabs/TabPanel.vue'
|
|
72
|
+
import ContentTypeSelect from './ContentTypeSelect.vue'
|
|
73
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
74
|
+
import SchemaPanel from './SchemaPanel.vue'
|
|
75
|
+
import type { OpenAPIV3 } from 'openapi-types'
|
|
76
|
+
|
|
77
|
+
const props = defineProps<{
|
|
78
|
+
responses: Record<string, OpenAPIV3.ResponseObject>
|
|
79
|
+
spec: OpenAPIV3.Document
|
|
80
|
+
}>()
|
|
81
|
+
|
|
82
|
+
const { t } = useTranslation()
|
|
83
|
+
|
|
84
|
+
const tabListContainer = useTemplateRef('tabListContainer')
|
|
85
|
+
const canScrollRight = ref(false)
|
|
86
|
+
|
|
87
|
+
function checkOverflow() {
|
|
88
|
+
const el = tabListContainer.value
|
|
89
|
+
if (!el) return
|
|
90
|
+
canScrollRight.value = el.scrollLeft + el.clientWidth < el.scrollWidth - 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function onScroll() {
|
|
94
|
+
checkOverflow()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onMounted(checkOverflow)
|
|
98
|
+
|
|
99
|
+
const tabs = computed(() =>
|
|
100
|
+
Object.entries(props.responses).map(([code, response]) => ({ code, response })),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
watch(tabs, () => {
|
|
104
|
+
nextTick(checkOverflow)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const activeTabIndex = ref(0)
|
|
108
|
+
const selectedContentType = ref('')
|
|
109
|
+
|
|
110
|
+
const currentContentTypes = computed(() => {
|
|
111
|
+
const tab = tabs.value[activeTabIndex.value]
|
|
112
|
+
if (!tab?.response.content) return []
|
|
113
|
+
return Object.keys(tab.response.content)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const currentMediaType = computed(() => {
|
|
117
|
+
const tab = tabs.value[activeTabIndex.value]
|
|
118
|
+
if (!tab?.response.content) return null
|
|
119
|
+
const ct = selectedContentType.value || currentContentTypes.value[0]
|
|
120
|
+
if (!ct) return null
|
|
121
|
+
return tab.response.content[ct] || null
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
watch(currentContentTypes, (types) => {
|
|
125
|
+
selectedContentType.value = types[0] || ''
|
|
126
|
+
}, { immediate: true })
|
|
127
|
+
|
|
128
|
+
function onTabChange(index: number) {
|
|
129
|
+
activeTabIndex.value = index
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function statusDotColor(code: string): string {
|
|
133
|
+
if (code.startsWith('2')) return 'bg-green-600'
|
|
134
|
+
if (code.startsWith('3')) return 'bg-blue-600'
|
|
135
|
+
if (code.startsWith('4')) return 'bg-orange-500'
|
|
136
|
+
if (code.startsWith('5')) return 'bg-red-600'
|
|
137
|
+
return 'bg-gray-400'
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<style scoped>
|
|
142
|
+
.scroll-fade {
|
|
143
|
+
mask-image: linear-gradient(to right, black calc(100% - 60px), transparent);
|
|
144
|
+
mask-size: 100% 100%;
|
|
145
|
+
mask-position: center;
|
|
146
|
+
padding-block: 4px;
|
|
147
|
+
margin-block: -4px;
|
|
148
|
+
}
|
|
149
|
+
</style>
|