@datagouv/components-next 0.2.0 → 1.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 +1 -1
- package/assets/main.css +49 -22
- package/dist/Control-BNCDn-8E.js +148 -0
- package/dist/{Datafair.client-x39O4yfF.js → Datafair.client-B5lBpOl8.js} +2 -2
- package/dist/Event-BOgJUhNR.js +738 -0
- package/dist/Image-BN-4XkIn.js +247 -0
- package/dist/{JsonPreview.client-BMsC5JcY.js → JsonPreview.client-Doz1Z0BS.js} +23 -23
- package/dist/Map-BdT3i2C4.js +7609 -0
- package/dist/MapContainer.client-oiieO8H-.js +105 -0
- package/dist/OSM-CamriM9b.js +71 -0
- package/dist/PdfPreview.client-CdAhkDFJ.js +14513 -0
- package/dist/{Pmtiles.client-BaiIo4VZ.js → Pmtiles.client-B0v8tGJQ.js} +3 -3
- package/dist/ScaleLine-BiesrgOv.js +165 -0
- package/dist/Swagger.client-CsK65JnG.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-CrjHf74q.js} +17 -17
- package/dist/common-C4rDcQpp.js +243 -0
- package/dist/components-next.css +1 -1
- package/dist/components-next.js +158 -117
- package/dist/components.css +1 -1
- package/dist/{MapContainer.client-DeSo8EvG.js → index-Bbu9rOHt.js} +4975 -21416
- package/dist/leaflet-src-7m1mB8LI.js +6338 -0
- package/dist/{main-Dgri3TQL.js → main-CiH8ZmBI.js} +56973 -51462
- 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-Bi_bsV6C.js} +1 -1
- package/package.json +6 -2
- 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 +85 -120
- 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/DatasetQuality.vue +23 -16
- package/src/components/DatasetQualityInline.vue +13 -17
- package/src/components/DatasetQualityScore.vue +12 -15
- 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/OrganizationHorizontalCard.vue +87 -0
- package/src/components/OrganizationLogo.vue +1 -1
- package/src/components/OrganizationNameWithCertificate.vue +12 -6
- 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/ProgressBar.vue +31 -0
- package/src/components/RadioGroup.vue +32 -0
- package/src/components/RadioInput.vue +64 -0
- package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
- package/src/components/ResourceAccordion/EditButton.vue +2 -3
- package/src/components/ResourceAccordion/JsonPreview.client.vue +3 -3
- package/src/components/ResourceAccordion/MapContainer.client.vue +21 -17
- package/src/components/ResourceAccordion/Metadata.vue +11 -24
- package/src/components/ResourceAccordion/PdfPreview.client.vue +70 -74
- package/src/components/ResourceAccordion/Pmtiles.client.vue +2 -2
- package/src/components/ResourceAccordion/Preview.vue +2 -2
- package/src/components/ResourceAccordion/ResourceAccordion.vue +35 -28
- package/src/components/ResourceAccordion/ResourceIcon.vue +1 -0
- package/src/components/ResourceAccordion/SchemaBadge.vue +2 -2
- package/src/components/ResourceAccordion/XmlPreview.client.vue +3 -3
- package/src/components/ResourceExplorer/ResourceExplorer.vue +243 -0
- package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +116 -0
- package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +410 -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 +49 -0
- package/src/components/Search/Filter/ReuseTypeFilter.vue +42 -0
- package/src/components/Search/GlobalSearch.vue +707 -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 +11 -4
- 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/useHasTabularData.ts +15 -0
- package/src/composables/useMetrics.ts +4 -3
- package/src/composables/useResourceCapabilities.ts +131 -0
- package/src/composables/useRouteQueryBoolean.ts +10 -0
- package/src/composables/useSelectModelSync.ts +89 -0
- package/src/composables/useStableQueryParams.ts +84 -0
- package/src/composables/useTranslation.ts +2 -1
- package/src/config.ts +4 -0
- package/src/functions/api.ts +25 -6
- package/src/functions/api.types.ts +5 -3
- 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/functions/resourceCapabilities.ts +55 -0
- package/src/main.ts +96 -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 +16 -0
- package/src/types/reuses.ts +14 -5
- package/src/types/search.ts +407 -0
- package/src/types/users.ts +12 -3
- package/dist/PdfPreview.client-COOkEkRA.js +0 -107
- package/dist/Swagger.client-CpLgaLg6.js +0 -4
- package/dist/pdf-vue3-IkJO65RH.js +0 -273
- package/dist/pdf.min-f72cfa08-CdgJTooZ.js +0 -9501
- package/src/components/DatasetInformationPanel.vue +0 -211
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="allResources.length || hasAnyResources">
|
|
3
|
+
<div class="flex gap-6">
|
|
4
|
+
<div class="flex-1 min-w-0">
|
|
5
|
+
<ResourceExplorerViewer
|
|
6
|
+
v-if="selectedResource && allResources.length"
|
|
7
|
+
:key="selectedResource.id"
|
|
8
|
+
:dataset
|
|
9
|
+
:resource="selectedResource"
|
|
10
|
+
/>
|
|
11
|
+
<div
|
|
12
|
+
v-else-if="search"
|
|
13
|
+
class="flex flex-col items-center py-12"
|
|
14
|
+
>
|
|
15
|
+
<slot name="no-results-image">
|
|
16
|
+
<img
|
|
17
|
+
:src="noResultsImage"
|
|
18
|
+
class="h-20"
|
|
19
|
+
alt=""
|
|
20
|
+
>
|
|
21
|
+
</slot>
|
|
22
|
+
<p class="fr-text--bold fr-my-3v">
|
|
23
|
+
{{ t('Pas de résultats pour « {q} »', { q: search }) }}
|
|
24
|
+
</p>
|
|
25
|
+
<BrandedButton
|
|
26
|
+
color="primary"
|
|
27
|
+
@click="updateSearch('')"
|
|
28
|
+
>
|
|
29
|
+
{{ t('Réinitialiser la recherche') }}
|
|
30
|
+
</BrandedButton>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<ResourceExplorerSidebar
|
|
34
|
+
:resources="allResources"
|
|
35
|
+
:selected-resource-id="selectedResource?.id ?? null"
|
|
36
|
+
:collapsed="sidebarCollapsed"
|
|
37
|
+
:search
|
|
38
|
+
@select="selectResource"
|
|
39
|
+
@load-more="loadMore"
|
|
40
|
+
@update:collapsed="sidebarCollapsed = $event"
|
|
41
|
+
@update:search="updateSearch($event)"
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div
|
|
46
|
+
v-else
|
|
47
|
+
class="flex flex-col items-center py-12"
|
|
48
|
+
>
|
|
49
|
+
<slot name="empty-image">
|
|
50
|
+
<img
|
|
51
|
+
:src="noResultsImage"
|
|
52
|
+
class="h-20"
|
|
53
|
+
alt=""
|
|
54
|
+
>
|
|
55
|
+
</slot>
|
|
56
|
+
<p class="fr-text--bold fr-my-3v">
|
|
57
|
+
{{ t('Ce jeu de données ne contient aucune ressource.') }}
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
</template>
|
|
61
|
+
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import { computed, ref, watch, type Ref } from 'vue'
|
|
64
|
+
import { useRouter } from 'vue-router'
|
|
65
|
+
import { useRouteQuery } from '@vueuse/router'
|
|
66
|
+
import { useComponentsConfig } from '../../config'
|
|
67
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
68
|
+
import { useDebouncedRef } from '../../composables/useDebouncedRef'
|
|
69
|
+
import { useFetch } from '../../functions/api'
|
|
70
|
+
import { RESOURCE_TYPE } from '../../functions/resources'
|
|
71
|
+
import type { PaginatedArray } from '../../types/api'
|
|
72
|
+
import type { DatasetV2 } from '../../types/datasets'
|
|
73
|
+
import type { Resource, ResourceGroup, ResourceType } from '../../types/resources'
|
|
74
|
+
import ResourceExplorerSidebar from './ResourceExplorerSidebar.vue'
|
|
75
|
+
import ResourceExplorerViewer from './ResourceExplorerViewer.vue'
|
|
76
|
+
import BrandedButton from '../BrandedButton.vue'
|
|
77
|
+
|
|
78
|
+
const props = withDefaults(defineProps<{
|
|
79
|
+
dataset: DatasetV2
|
|
80
|
+
noResultsImage?: string
|
|
81
|
+
}>(), {
|
|
82
|
+
noResultsImage: '',
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const { t } = useTranslation()
|
|
86
|
+
const router = useRouter()
|
|
87
|
+
const config = useComponentsConfig()
|
|
88
|
+
|
|
89
|
+
const sidebarCollapsed = ref(false)
|
|
90
|
+
const search = ref('')
|
|
91
|
+
const { debounced: searchDebounced, flush } = useDebouncedRef(search, config.searchDebounce ?? 300)
|
|
92
|
+
|
|
93
|
+
const PAGE_SIZE = 10
|
|
94
|
+
const url = computed(() => `/api/2/datasets/${props.dataset.id}/resources/`)
|
|
95
|
+
|
|
96
|
+
// Resource ID from URL query
|
|
97
|
+
const resourceIdQuery = useRouteQuery<string | undefined>('resource_id')
|
|
98
|
+
|
|
99
|
+
// Fetch resources for each type
|
|
100
|
+
const mainParams = computed(() => ({
|
|
101
|
+
type: 'main' as const,
|
|
102
|
+
page_size: PAGE_SIZE,
|
|
103
|
+
q: searchDebounced.value || undefined,
|
|
104
|
+
}))
|
|
105
|
+
const documentationParams = computed(() => ({
|
|
106
|
+
type: 'documentation' as const,
|
|
107
|
+
page_size: PAGE_SIZE,
|
|
108
|
+
q: searchDebounced.value || undefined,
|
|
109
|
+
}))
|
|
110
|
+
const updateParams = computed(() => ({
|
|
111
|
+
type: 'update' as const,
|
|
112
|
+
page_size: PAGE_SIZE,
|
|
113
|
+
q: searchDebounced.value || undefined,
|
|
114
|
+
}))
|
|
115
|
+
const apiParams = computed(() => ({
|
|
116
|
+
type: 'api' as const,
|
|
117
|
+
page_size: PAGE_SIZE,
|
|
118
|
+
q: searchDebounced.value || undefined,
|
|
119
|
+
}))
|
|
120
|
+
const codeParams = computed(() => ({
|
|
121
|
+
type: 'code' as const,
|
|
122
|
+
page_size: PAGE_SIZE,
|
|
123
|
+
q: searchDebounced.value || undefined,
|
|
124
|
+
}))
|
|
125
|
+
const otherParams = computed(() => ({
|
|
126
|
+
type: 'other' as const,
|
|
127
|
+
page_size: PAGE_SIZE,
|
|
128
|
+
q: searchDebounced.value || undefined,
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
const { data: mainData, status: mainStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: mainParams })
|
|
132
|
+
const { data: documentationData, status: documentationStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: documentationParams, server: false })
|
|
133
|
+
const { data: updateData, status: updateStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: updateParams, server: false })
|
|
134
|
+
const { data: apiData, status: apiStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: apiParams, server: false })
|
|
135
|
+
const { data: codeData, status: codeStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: codeParams, server: false })
|
|
136
|
+
const { data: otherData, status: otherStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: otherParams, server: false })
|
|
137
|
+
|
|
138
|
+
const rawResourcesByTypes = [
|
|
139
|
+
{ data: mainData, status: mainStatus },
|
|
140
|
+
{ data: documentationData, status: documentationStatus },
|
|
141
|
+
{ data: updateData, status: updateStatus },
|
|
142
|
+
{ data: apiData, status: apiStatus },
|
|
143
|
+
{ data: codeData, status: codeStatus },
|
|
144
|
+
{ data: otherData, status: otherStatus },
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
// Evaluated once at setup (before any search) — never changes afterwards
|
|
148
|
+
const hasAnyResources = computed(() => {
|
|
149
|
+
return props.dataset.resources.total > 0
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const extraResourcesByType: Ref<Resource[]>[] = RESOURCE_TYPE.map(() => ref<Resource[]>([]))
|
|
153
|
+
const pageByType: Ref<number>[] = RESOURCE_TYPE.map(() => ref(1))
|
|
154
|
+
|
|
155
|
+
watch(searchDebounced, () => {
|
|
156
|
+
for (let i = 0; i < RESOURCE_TYPE.length; i++) {
|
|
157
|
+
extraResourcesByType[i]!.value = []
|
|
158
|
+
pageByType[i]!.value = 1
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const loadMore = async (type: ResourceType) => {
|
|
163
|
+
const index = RESOURCE_TYPE.indexOf(type)
|
|
164
|
+
if (index === -1) return
|
|
165
|
+
const pageRef = pageByType[index]!
|
|
166
|
+
const extraRef = extraResourcesByType[index]!
|
|
167
|
+
pageRef.value++
|
|
168
|
+
|
|
169
|
+
const { data } = await useFetch<PaginatedArray<Resource>>(url, {
|
|
170
|
+
params: {
|
|
171
|
+
type,
|
|
172
|
+
page_size: PAGE_SIZE,
|
|
173
|
+
page: pageRef.value,
|
|
174
|
+
q: searchDebounced.value || undefined,
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
if (data.value) {
|
|
179
|
+
extraRef.value = [...extraRef.value, ...data.value.data]
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const allResources = computed<ResourceGroup[]>(() => {
|
|
184
|
+
return RESOURCE_TYPE
|
|
185
|
+
.map((type, index) => {
|
|
186
|
+
const rawData = rawResourcesByTypes[index]!
|
|
187
|
+
const extraData = extraResourcesByType[index]!
|
|
188
|
+
return {
|
|
189
|
+
type: type as ResourceType,
|
|
190
|
+
total: rawData.data.value?.total ?? 0,
|
|
191
|
+
items: [...(rawData.data.value?.data ?? []), ...extraData.value],
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.filter(group => group.items.length > 0)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const flatResources = computed(() =>
|
|
198
|
+
allResources.value.flatMap(g => g.items),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
// Fetch resource by ID if specified in URL (for SSR)
|
|
202
|
+
const initialResourceId = resourceIdQuery.value
|
|
203
|
+
const { data: fetchedResource } = initialResourceId
|
|
204
|
+
? await useFetch<Resource>(`/api/1/datasets/${props.dataset.id}/resources/${initialResourceId}/`)
|
|
205
|
+
: { data: ref(null) }
|
|
206
|
+
|
|
207
|
+
// Initial selection (synchronous for SSR hydration)
|
|
208
|
+
function getInitialResource(): Resource | null {
|
|
209
|
+
const resourceId = resourceIdQuery.value
|
|
210
|
+
if (resourceId) {
|
|
211
|
+
// First check in already loaded resources
|
|
212
|
+
const existing = flatResources.value.find(r => r.id === resourceId)
|
|
213
|
+
if (existing) return existing
|
|
214
|
+
// Use fetched resource if available
|
|
215
|
+
if (fetchedResource.value) return fetchedResource.value
|
|
216
|
+
}
|
|
217
|
+
// Default to first resource
|
|
218
|
+
return flatResources.value[0] ?? null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const selectedResource = ref<Resource | null>(getInitialResource())
|
|
222
|
+
|
|
223
|
+
function updateSearch(newSearch: string) {
|
|
224
|
+
search.value = newSearch
|
|
225
|
+
flush()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const selectResource = (resource: Resource) => {
|
|
229
|
+
selectedResource.value = resource
|
|
230
|
+
router.replace({
|
|
231
|
+
query: { ...router.currentRoute.value.query, resource_id: resource.id },
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Update selection when resources change (e.g., after client-side fetch completes)
|
|
236
|
+
watch(flatResources, () => {
|
|
237
|
+
if (selectedResource.value) return
|
|
238
|
+
const firstResource = flatResources.value[0]
|
|
239
|
+
if (firstResource) {
|
|
240
|
+
selectedResource.value = firstResource
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
</script>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<aside
|
|
3
|
+
v-if="!collapsed"
|
|
4
|
+
class="w-72 shrink-0 pl-4"
|
|
5
|
+
>
|
|
6
|
+
<div class="flex items-center justify-between mb-3">
|
|
7
|
+
<h3 class="text-sm font-bold uppercase mb-0">
|
|
8
|
+
{{ t('Ressources') }}
|
|
9
|
+
</h3>
|
|
10
|
+
<button
|
|
11
|
+
type="button"
|
|
12
|
+
:title="t('Masquer le panneau')"
|
|
13
|
+
class="p-1 hover:bg-gray-100 rounded"
|
|
14
|
+
@click="$emit('update:collapsed', true)"
|
|
15
|
+
>
|
|
16
|
+
<RiArrowRightSLine class="size-5" />
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="mb-3">
|
|
21
|
+
<label
|
|
22
|
+
:for="searchId"
|
|
23
|
+
class="sr-only"
|
|
24
|
+
>{{ t('Rechercher') }}</label>
|
|
25
|
+
<input
|
|
26
|
+
:id="searchId"
|
|
27
|
+
:value="search"
|
|
28
|
+
type="search"
|
|
29
|
+
class="w-full border border-gray-default rounded px-2.5 py-1.5 text-sm"
|
|
30
|
+
:placeholder="t('Rechercher')"
|
|
31
|
+
@input="$emit('update:search', ($event.target as HTMLInputElement).value)"
|
|
32
|
+
>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="space-y-4 overflow-y-auto">
|
|
36
|
+
<div
|
|
37
|
+
v-for="group in resources"
|
|
38
|
+
:key="group.type"
|
|
39
|
+
>
|
|
40
|
+
<div class="text-xs text-gray-plain font-bold uppercase mb-1.5 border-b border-gray-default pb-1">
|
|
41
|
+
{{ getResourceLabel(group.type, group.total) }}
|
|
42
|
+
</div>
|
|
43
|
+
<ul class="list-none p-0 m-0 space-y-0.5">
|
|
44
|
+
<li
|
|
45
|
+
v-for="resource in group.items"
|
|
46
|
+
:key="resource.id"
|
|
47
|
+
>
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
class="w-full text-left px-2 py-1.5 rounded text-sm flex items-center gap-1.5 hover:bg-gray-100"
|
|
51
|
+
:class="{
|
|
52
|
+
'font-bold bg-blue-50': resource.id === selectedResourceId,
|
|
53
|
+
}"
|
|
54
|
+
@click="$emit('select', resource)"
|
|
55
|
+
>
|
|
56
|
+
<ResourceIcon
|
|
57
|
+
:resource
|
|
58
|
+
class="size-3.5 shrink-0"
|
|
59
|
+
/>
|
|
60
|
+
<span class="truncate">{{ resource.title || t('Fichier sans nom') }}</span>
|
|
61
|
+
</button>
|
|
62
|
+
</li>
|
|
63
|
+
</ul>
|
|
64
|
+
<button
|
|
65
|
+
v-if="group.items.length < group.total"
|
|
66
|
+
type="button"
|
|
67
|
+
class="w-full text-left px-2 py-1.5 text-sm text-blue-default hover:underline"
|
|
68
|
+
@click="$emit('load-more', group.type)"
|
|
69
|
+
>
|
|
70
|
+
{{ t('Charger plus…') }}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</aside>
|
|
75
|
+
|
|
76
|
+
<div
|
|
77
|
+
v-else
|
|
78
|
+
class="shrink-0 flex items-start pt-1"
|
|
79
|
+
>
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
:title="t('Afficher le panneau des ressources')"
|
|
83
|
+
class="p-1 hover:bg-gray-100 rounded border border-gray-default"
|
|
84
|
+
@click="$emit('update:collapsed', false)"
|
|
85
|
+
>
|
|
86
|
+
<RiArrowLeftSLine class="size-5" />
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<script setup lang="ts">
|
|
92
|
+
import { useId } from 'vue'
|
|
93
|
+
import { RiArrowRightSLine, RiArrowLeftSLine } from '@remixicon/vue'
|
|
94
|
+
import ResourceIcon from '../ResourceAccordion/ResourceIcon.vue'
|
|
95
|
+
import { getResourceLabel } from '../../functions/resources'
|
|
96
|
+
import { useTranslation } from '../../composables/useTranslation'
|
|
97
|
+
import type { Resource, ResourceGroup, ResourceType } from '../../types/resources'
|
|
98
|
+
|
|
99
|
+
const { t } = useTranslation()
|
|
100
|
+
|
|
101
|
+
defineProps<{
|
|
102
|
+
resources: ResourceGroup[]
|
|
103
|
+
selectedResourceId: string | null
|
|
104
|
+
collapsed: boolean
|
|
105
|
+
search: string
|
|
106
|
+
}>()
|
|
107
|
+
|
|
108
|
+
defineEmits<{
|
|
109
|
+
'select': [resource: Resource]
|
|
110
|
+
'load-more': [type: ResourceType]
|
|
111
|
+
'update:collapsed': [value: boolean]
|
|
112
|
+
'update:search': [value: string]
|
|
113
|
+
}>()
|
|
114
|
+
|
|
115
|
+
const searchId = useId()
|
|
116
|
+
</script>
|