API-Explorer-II/src/components/SearchNav.vue
2025-03-13 15:42:41 +01:00

303 lines
9.1 KiB
Vue

<!--
- Open Bank Project - API Explorer II
- Copyright (C) 2023-2024, TESOBE GmbH
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- Email: contact@tesobe.com
- TESOBE GmbH
- Osloerstrasse 16/17
- Berlin 13359, Germany
-
- This product includes software developed at
- TESOBE (http://www.tesobe.com/)
-
-->
<script lang="ts">
import { obpResourceDocsKey } from '@/obp/keys'
import { Search } from '@element-plus/icons-vue'
import { inject, onBeforeMount, onMounted, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { OBP_API_DEFAULT_RESOURCE_DOC_VERSION, getMyAPICollections, getMyAPICollectionsEndpoint } from '../obp'
import { getGroupedResourceDocs, getFilteredGroupedResourceDocs } from '../obp/resource-docs'
import { SEARCH_LINKS_COLOR as searchLinksColorSetting } from '../obp/style-setting'
const operationIdTitle = {}
const resourceDocs = ref({})
const docs = ref({})
const groups = ref({})
const sortedKeys = ref([])
const activeKeys = ref([])
const showMyCollections = ref(false)
const form = reactive({
search: ''
})
const apiCollections = ref({})
const apiCollectionsEndpointMapping = ref({})
const apiCollectionsEndpoint = ref({})
const searchLinksColor = ref(searchLinksColorSetting)
const clearActiveTab = () => {
const activeTabs = document.querySelectorAll('.active-api-router-tab')
activeTabs.forEach((tab) => {
tab.classList.remove('active-api-router-tab')
})
}
export const setTabActive = (id) => {
const tabs = document.querySelectorAll('.api-router-link')
clearActiveTab()
tabs.forEach((tab) => {
if (tab.id === id) {
tab.parentElement.classList.add('active-api-router-tab')
}
})
}
export const initializeAPICollections = async () => {
apiCollections.value = (await getMyAPICollections()).api_collections
if (apiCollections.value) {
showMyCollections.value = true
for (const { api_collection_name } of apiCollections.value) {
apiCollectionsEndpoint.value[api_collection_name] = (
await getMyAPICollectionsEndpoint(api_collection_name)
).api_collection_endpoints.map((api) => api.operation_id)
}
}
}
</script>
<script setup lang="ts">
const route = useRoute()
let selectedVersion = route.query.version ? route.query.version : `${OBP_API_DEFAULT_RESOURCE_DOC_VERSION}`
let selectedTags = route.query.tags ? route.query.tags : 'NONE'
onBeforeMount(async () => {
resourceDocs.value = inject(obpResourceDocsKey)!
if(selectedTags === 'NONE') {
docs.value = getGroupedResourceDocs(selectedVersion, resourceDocs.value)
} else {
docs.value = getFilteredGroupedResourceDocs(selectedVersion, selectedTags, resourceDocs.value)
}
groups.value = JSON.parse(JSON.stringify(docs.value))
activeKeys.value = Object.keys(groups.value)
sortedKeys.value = activeKeys.value.sort()
await initializeAPICollections()
setTabActive(route.params.id)
let element = document.getElementById("selected-api-version")
if (element !== null) {
const totalRows = Object.values(groups.value).reduce((acc, currentValue) => acc + currentValue.length, 0)
if(selectedTags === 'NONE') {
element.textContent = `${selectedVersion} ( ${totalRows} APIs )`;
} else {
element.textContent = `${selectedVersion} ( ${totalRows} APIs filtered by tags: ${selectedTags})`;
}
}
})
onMounted(() => {
routeToFirstAPI()
})
watch(
() => route.query.version,
async (version) => {
selectedVersion = version
docs.value = getGroupedResourceDocs(version, resourceDocs.value)
groups.value = JSON.parse(JSON.stringify(docs.value))
activeKeys.value = Object.keys(groups.value)
sortedKeys.value = activeKeys.value.sort()
await initializeAPICollections()
routeToFirstAPI()
countApis()
}
)
const countApis = () => {
let element = document.getElementById("selected-api-version")
if (element !== null) {
const totalRows = Object.values(groups.value).reduce((acc, currentValue) => acc + currentValue.length, 0)
element.textContent = `${selectedVersion} ( ${totalRows} APIs )`;
}
}
const routeToFirstAPI = () => {
let element
const elements = document.getElementsByClassName('api-router-link')
const id = route.params.id
for (const el of elements) {
if (el.id === id) {
element = el
break
}
}
if (element) {
element.click()
} else {
if (elements.item(0)) elements.item(0).click()
}
}
const sortLinks = (items: any) => {
const uniqueLinks = {}
for (const { summary, operation_id } of items) {
if (!Object.keys(uniqueLinks).includes(summary.trim()))
uniqueLinks[summary.trim()] = operation_id
operationIdTitle[operation_id] = summary.trim()
}
const sortResult = Object.fromEntries(
Object.entries(uniqueLinks).sort((a, b) => {
if (a[0] < b[0]) {
return -1
}
if (a[0] > b[0]) {
return 1
}
return 0
})
)
return sortResult
}
const setActive = (event) => {
const target = event.target
if (target.tagName.toLowerCase() === 'a') {
setTabActive(target.id)
}
}
const isKeyFound = (keys, item) => keys.every((k) => item.toLowerCase().includes(k))
const filterKeys = (keys, key) => {
const splitKey = key.split(' ').map((k) => k.toLowerCase())
return keys.filter((title) => {
const isGroupFound = isKeyFound(splitKey, title)
const items = docs.value[title].filter(
(item) => isGroupFound || isKeyFound(splitKey, item.summary)
)
groups.value[title] = items
return isGroupFound || items.length > 0
})
}
const searchEvent = (value) => {
if (value) {
sortedKeys.value = filterKeys(activeKeys.value, value)
} else {
groups.value = JSON.parse(JSON.stringify(docs.value))
sortedKeys.value = Object.keys(groups.value).sort()
}
}
</script>
<template>
<el-container class="search-nav-container">
<el-header class="search-nav-search-bar">
<el-col :span="24">
<el-input v-model="form.search" placeholder="Search" :prefix-icon="Search" @input="searchEvent" />
</el-col>
</el-header>
<el-main>
<el-collapse v-model="activeKeys" class="search-nav-collapse">
<el-collapse-item title="My Collections" v-show="showMyCollections" name="my-collections">
<el-collapse-item v-for="(api, key) of apiCollections" :key="key" :title="api.api_collection_name"
:name="api.api_collection_name" class="child-collapse">
<div class="el-tabs--right">
<div v-for="(value, key) of apiCollectionsEndpoint[api.api_collection_name]" :key="key" class="api-router-tab"
@click="setActive">
<RouterLink :to="{ name: 'api', params: { id: value }, query: { version: selectedVersion } }" :id="value"
active-class="active-api-router-link" class="api-router-link">{{ operationIdTitle[value] }}</RouterLink>
</div>
</div>
</el-collapse-item>
</el-collapse-item>
<el-collapse-item v-for="key in sortedKeys" :title="key" :key="key" :name="key">
<div class="el-tabs--right">
<div v-for="(value, key) of sortLinks(groups[key])" :key="value" class="api-router-tab" @click="setActive">
<RouterLink active-class="active-api-router-link" class="api-router-link" :id="value"
:to="{ name: 'api', params: { id: value }, query: { version: selectedVersion } }">{{ key }}</RouterLink>
</div>
</div>
</el-collapse-item>
</el-collapse>
</el-main>
</el-container>
</template>
<style scoped>
.search-nav-container {
height: 100%;
max-height: 100%;
overflow: hidden;
padding: 0;
}
.search-nav {
background-color: #f8f9fb;
max-height: 100%;
padding-right: 0;
border-right: solid 1px var(--el-menu-border-color);
}
.search-nav-collapse {
height: 100%;
max-height: 99%;
margin-right: -10px;
width: 100%;
overflow-x: hidden;
min-height: unset;
}
.search-nav-search-bar {
box-shadow: rgba(0, 0, 0, 0.40) 0px 25px 50px -20px;
height: auto;
}
.api-router-link {
width: 100%;
margin-left: 15px;
font-family: 'Roboto';
text-decoration: none;
color: #39455f;
display: inline-block;
}
.api-router-tab {
border-left: 2px solid var(--el-menu-border-color);
}
.api-router-tab:hover,
.active-api-router-tab {
border-left: 2px solid v-bind(searchLinksColor);
}
.api-router-tab:hover .api-router-link,
.active-api-router-link {
color: v-bind(searchLinksColor);
}
.favorite {
cursor: pointer;
line-height: 2;
font-size: 18px;
vertical-align: middle;
text-align: center;
padding: 12px;
color: #39455f;
}
.child-collapse {
margin-left: 15px;
}
</style>