Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 68 additions & 18 deletions components/OpenApi/OpenApiProperties.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,43 @@
>
{{ $t('Aucune réponse documentée dans le swagger.') }}
</p>
<div
v-for="endpoint in data"
v-else
:key="`${endpoint.method}-${endpoint.path}`"
class="mb-4 bg-white border border-gray-200 rounded-sm p-4"
>
<h3 class="text-base font-bold mb-3">
{{ endpoint.summary || endpoint.path }}
</h3>
<hr class="mb-4 border-gray-200">
<OpenApiProperty
v-for="(schema, name) in endpoint.properties"
:key="name"
:name="String(name)"
:schema="schema"
/>
</div>
<template v-else>
<div
v-if="hasExpandableNode"
class="flex gap-2 mb-4"
>
<BrandedButton
color="secondary"
size="xs"
@click="openAll"
>
{{ $t('Ouvrir tout') }}
</BrandedButton>
<BrandedButton
color="secondary"
size="xs"
@click="closeAll"
>
{{ $t('Fermer tout') }}
</BrandedButton>
</div>
<div
v-for="endpoint in data"
:key="`${endpoint.method}-${endpoint.path}`"
class="mb-4 bg-white border border-gray-200 rounded-sm p-4"
>
<h3 class="text-base font-bold mb-3">
{{ endpoint.summary || endpoint.path }}
</h3>
<hr class="mb-4 border-gray-200">
<OpenApiProperty
v-for="(schema, name) in endpoint.properties"
:key="name"
:name="String(name)"
:schema="schema"
/>
</div>
</template>
</template>
<template #error>
<p class="text-sm text-red-600">
Expand All @@ -39,10 +59,12 @@
</template>

<script setup lang="ts">
import { computed, provide, reactive } from 'vue'
import { parse } from 'yaml'
import type { OpenAPI } from 'openapi-types'
import { LoadingBlock } from '@datagouv/components-next'
import { BrandedButton, LoadingBlock } from '@datagouv/components-next'
import OpenApiProperty from './OpenApiProperty.vue'
import { collapseSignalKey, type CollapseSignal } from './openApiCollapse'
import { extractEndpoints, type EndpointProperties } from '~/utils/openapi-extract'
import { unwrapBouquetData, filterEndpointsByTitle } from '~/utils/openapi-bouquet'

Expand All @@ -67,4 +89,32 @@ const { data: endpoints, status } = await useAsyncData<EndpointProperties[]>(
},
{ lazy: true, server: false },
)

const collapseSignal = reactive<CollapseSignal>({ target: true, version: 0 })
provide(collapseSignalKey, collapseSignal)

function openAll() {
collapseSignal.target = true
collapseSignal.version++
}

function closeAll() {
collapseSignal.target = false
collapseSignal.version++
}

function isObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v)
}

function nodeHasChildren(schema: unknown): boolean {
if (!isObject(schema)) return false
if (schema.type === 'object' && isObject(schema.properties)) return true
if (schema.type === 'array' && isObject(schema.items) && isObject(schema.items.properties)) return true
return false
}

const hasExpandableNode = computed(() =>
(endpoints.value ?? []).some(endpoint => Object.values(endpoint.properties).some(nodeHasChildren)),
)
</script>
56 changes: 43 additions & 13 deletions components/OpenApi/OpenApiProperty.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
<template>
<div class="mb-4">
<div class="flex items-center justify-between mb-1">
<span class="inline-block rounded-sm border border-gray-200 bg-white px-3 py-1 text-sm font-bold shrink-0">
{{ title || name }}
</span>
<div class="flex items-center mb-1">
<button
v-if="isExpandable"
type="button"
class="flex items-center gap-1 shrink-0 min-w-0"
:aria-expanded="isOpen"
@click="isOpen = !isOpen"
>
<RiArrowRightSLine
class="size-4 text-gray-500 shrink-0 transition-transform"
:class="{ 'rotate-90': isOpen }"
/>
<span class="inline-block rounded-sm border border-gray-200 bg-white px-3 py-1 text-sm font-bold truncate">
{{ title || name }}
</span>
</button>
<span
v-if="example !== undefined"
class="text-xs text-gray-500 ml-2 truncate"
v-else
class="inline-block rounded-sm border border-gray-200 bg-white px-3 py-1 text-sm font-bold shrink-0 ml-5"
>
{{ $t('Ex : {example}', { example: String(example) }) }}
{{ title || name }}
</span>
</div>

Expand All @@ -22,13 +34,20 @@
<!-- eslint-disable-next-line vue/no-v-html -->
<div
v-else-if="description"
class="text-sm text-gray-600 mb-2 pl-3"
class="text-sm text-gray-800 mb-2 pl-3"
v-html="sanitizedDescription"
/>

<p
v-if="example !== undefined"
class="text-xs text-gray-500 mb-2 pl-3"
>
{{ $t('Ex : {example}', { example: String(example) }) }}
</p>

<div
v-if="objectProperties"
class="border-l border-gray-200 pl-4 ml-3 mt-2"
v-if="objectProperties && isOpen"
class="border-l border-gray-300 pl-4 ml-3 mt-2"
>
<OpenApiProperty
v-for="(subSchema, subName) in objectProperties"
Expand All @@ -39,8 +58,8 @@
</div>

<div
v-if="arrayItemProperties"
class="border-l border-gray-200 pl-4 ml-3 mt-2"
v-if="arrayItemProperties && isOpen"
class="border-l border-gray-300 pl-4 ml-3 mt-2"
>
<p class="text-xs text-gray-500 mb-2">
{{ $t('Cette propriété contient 1 ou plusieurs éléments ayant les spécifications suivantes :') }}
Expand All @@ -56,8 +75,10 @@
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { computed, inject, ref, watch } from 'vue'
import { RiArrowRightSLine } from '@remixicon/vue'
import DOMPurify from 'dompurify'
import { collapseSignalKey } from './openApiCollapse'

const props = defineProps<{
name: string
Expand Down Expand Up @@ -103,6 +124,15 @@ const arrayItemProperties = computed(() => {
return isObject(items.properties) ? items.properties : undefined
})

const isExpandable = computed(() => objectProperties.value !== undefined || arrayItemProperties.value !== undefined)

const isOpen = ref(true)

const collapseSignal = inject(collapseSignalKey, null)
watch(() => collapseSignal?.version, () => {
if (collapseSignal) isOpen.value = collapseSignal.target
})

const sanitizedDescription = computed(() => {
if (!description.value) return ''
return DOMPurify.sanitize(description.value, {
Expand Down
8 changes: 8 additions & 0 deletions components/OpenApi/openApiCollapse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { InjectionKey } from 'vue'

export type CollapseSignal = {
target: boolean
version: number
}

export const collapseSignalKey: InjectionKey<CollapseSignal> = Symbol('openApiCollapseSignal')
Loading