feat: smart-tree component added to hoppscotch-ui (#3210)

This commit is contained in:
Anwarul Islam
2023-08-20 21:18:32 +06:00
committed by GitHub
parent d5c887f311
commit d4d1e27ba9
8 changed files with 34 additions and 21 deletions

View File

@@ -1,69 +0,0 @@
<template>
<div class="flex flex-col flex-1">
<div
v-if="rootNodes.status === 'loaded' && rootNodes.data.length > 0"
class="flex flex-col"
>
<div
v-for="rootNode in rootNodes.data"
:key="rootNode.id"
class="flex flex-col flex-1"
>
<SmartTreeBranch
:root-nodes-length="rootNodes.data.length"
:node-item="rootNode"
:adapter="adapter as SmartTreeAdapter<T>"
>
<template
#default="{ node, toggleChildren, isOpen, highlightChildren }"
>
<slot
name="content"
:node="node as TreeNode<T>"
:toggle-children="toggleChildren as () => void"
:is-open="isOpen as boolean"
:highlight-children="(id:string|null) => highlightChildren(id)"
></slot>
</template>
<template #emptyNode="{ node }">
<slot name="emptyNode" :node="node"></slot>
</template>
</SmartTreeBranch>
</div>
</div>
<div
v-else-if="rootNodes.status === 'loading'"
class="flex flex-col items-center justify-center flex-1 p-4"
>
<HoppSmartSpinner class="my-4" />
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
</div>
<div
v-if="rootNodes.status === 'loaded' && rootNodes.data.length === 0"
class="flex flex-col flex-1"
>
<slot name="emptyNode" :node="(null as TreeNode<T> | null)"></slot>
</div>
</div>
</template>
<script setup lang="ts" generic="T extends any">
import { computed } from "vue"
import { useI18n } from "~/composables/i18n"
import { SmartTreeAdapter, TreeNode } from "~/helpers/treeAdapter"
const props = defineProps<{
/**
* The adapter that will be used to fetch the tree data
* @template T The type of the data that will be stored in the tree
*/
adapter: SmartTreeAdapter<T>
}>()
const t = useI18n()
/**
* Fetch the root nodes from the adapter by passing the node id as null
*/
const rootNodes = computed(() => props.adapter.getChildren(null).value)
</script>

View File

@@ -1,131 +0,0 @@
<template>
<slot
:node="nodeItem"
:toggle-children="toggleNodeChildren"
:is-open="isNodeOpen"
:highlight-children="(id:string|null) => highlightNodeChildren(id)"
></slot>
<!-- This is a performance optimization trick -->
<!-- Once expanded, Vue will traverse through the children and expand the tree up
but when we collapse, the tree and the components are disposed. This is wasteful
and comes with performance issues if the children list is expensive to render.
Hence, here we render children only when first expanded, and after that, even if collapsed,
we just hide the children.
-->
<div v-if="childrenRendered" v-show="showChildren" class="flex">
<div
class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-0.5 hover:bg-dividerDark hover:scale-x-125"
@click="toggleNodeChildren"
></div>
<div
v-if="childNodes.status === 'loaded' && childNodes.data.length > 0"
class="flex flex-col flex-1 truncate"
:class="{
'bg-divider': highlightNode,
}"
>
<TreeBranch
v-for="childNode in childNodes.data"
:key="childNode.id"
:node-item="childNode"
:adapter="adapter"
>
<!-- The child slot is given a dynamic name in order to not break Volar -->
<template
#[CHILD_SLOT_NAME]="{
node,
toggleChildren,
isOpen,
highlightChildren,
}"
>
<!-- Casting to help with type checking -->
<slot
:node="node as TreeNode<T>"
:toggle-children="toggleChildren as () => void"
:is-open="isOpen as boolean"
:highlight-children="(id:string|null) => highlightChildren(id) as void"
></slot>
</template>
<template #emptyNode="{ node }">
<slot name="emptyNode" :node="node"></slot>
</template>
</TreeBranch>
</div>
<div
v-if="childNodes.status === 'loading'"
class="flex flex-col items-center justify-center flex-1 p-4"
>
<HoppSmartSpinner class="my-4" />
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
</div>
<div
v-if="childNodes.status === 'loaded' && childNodes.data.length === 0"
class="flex flex-col flex-1"
>
<slot name="emptyNode" :node="nodeItem"></slot>
</div>
</div>
</template>
<script setup lang="ts" generic="T extends any">
import { computed, ref } from "vue"
import { useI18n } from "~/composables/i18n"
import { SmartTreeAdapter, TreeNode } from "~/helpers/treeAdapter"
const props = defineProps<{
/**
* The node item that will be used to render the tree branch
* @template T The type of the data passed to the tree branch
*/
adapter: SmartTreeAdapter<T>
/**
* The node item that will be used to render the tree branch content
*/
nodeItem: TreeNode<T>
/**
* Total number of rootNode
*/
rootNodesLength?: number
}>()
const CHILD_SLOT_NAME = "default"
const t = useI18n()
const isOnlyRootChild = computed(() => props.rootNodesLength === 1)
/**
* Marks whether the children on this branch were ever rendered
* See the usage inside '<template>' for more info
*/
const childrenRendered = ref(isOnlyRootChild.value)
const showChildren = ref(isOnlyRootChild.value)
const isNodeOpen = ref(isOnlyRootChild.value)
const highlightNode = ref(false)
/**
* Fetch the child nodes from the adapter by passing the node id of the current node
*/
const childNodes = computed(
() => props.adapter.getChildren(props.nodeItem.id).value
)
const toggleNodeChildren = () => {
if (!childrenRendered.value) childrenRendered.value = true
showChildren.value = !showChildren.value
isNodeOpen.value = !isNodeOpen.value
}
const highlightNodeChildren = (id: string | null) => {
if (id) {
highlightNode.value = true
} else {
highlightNode.value = false
}
}
</script>