mirror of
https://github.com/hoppscotch/hoppscotch.git
synced 2026-06-07 21:05:23 +08:00
fix(common): normalize search tree adapter for team collection shape [skip ci]
The search tree adapter was typed `Ref<HoppCollection[]>` but the teams provider forces a `Ref<TeamCollection[]>` into it via a double-cast (`as unknown as Ref<HoppCollection[]>` in teams.workspace.ts:1472). `HoppCollection` uses `folders` while `TeamCollection` uses `children`. Root-level rendering worked because both types expose a top-level `requests`, but expanding a nested team search hit would throw `TypeError: Cannot read properties of undefined (reading 'map')` on `item.folders.map`. Normalize access inside the adapter via a union-safe shape that reads either `folders` or `children`, and replace the personal-only `navigateToFolderWithIndexPath` walk with a local walker using the same accessor. Personal-workspace behavior is unchanged; team-workspace expansion now walks the nested tree correctly.
This commit is contained in:
parent
e49e0a0013
commit
609dfe84e7
@ -1,9 +1,39 @@
|
||||
import { HoppCollection } from "@hoppscotch/data"
|
||||
import { ChildrenResult, SmartTreeAdapter } from "@hoppscotch/ui"
|
||||
import { Ref, computed } from "vue"
|
||||
import { navigateToFolderWithIndexPath } from "~/newstore/collections"
|
||||
import { RESTCollectionViewItem } from "~/services/new-workspace/view"
|
||||
|
||||
// Shape covers both `HoppCollection` (personal — uses `folders`) and
|
||||
// `TeamCollection` (teams — uses `children`). Normalized at access time
|
||||
// so a single adapter handles both workspaces.
|
||||
type SearchTreeNode = {
|
||||
name?: string
|
||||
title?: string
|
||||
folders?: SearchTreeNode[] | null
|
||||
children?: SearchTreeNode[] | null
|
||||
requests?: Array<{ name?: string; title?: string; [key: string]: unknown }> | null
|
||||
}
|
||||
|
||||
const getChildCollections = (node: SearchTreeNode): SearchTreeNode[] =>
|
||||
node.folders ?? node.children ?? []
|
||||
|
||||
const getNodeName = (node: SearchTreeNode): string =>
|
||||
node.name ?? node.title ?? ""
|
||||
|
||||
// Local tree walk — does not depend on `folders` shape unlike the
|
||||
// personal-workspace `navigateToFolderWithIndexPath` helper.
|
||||
const navigateToNode = (
|
||||
roots: SearchTreeNode[],
|
||||
indexPath: number[]
|
||||
): SearchTreeNode | null => {
|
||||
if (indexPath.length === 0) return null
|
||||
let current: SearchTreeNode | undefined = roots[indexPath[0]]
|
||||
for (let i = 1; i < indexPath.length && current; i++) {
|
||||
current = getChildCollections(current)[indexPath[i]]
|
||||
}
|
||||
return current ?? null
|
||||
}
|
||||
|
||||
export class WorkspaceRESTSearchCollectionTreeAdapter
|
||||
implements SmartTreeAdapter<RESTCollectionViewItem>
|
||||
{
|
||||
@ -13,18 +43,20 @@ export class WorkspaceRESTSearchCollectionTreeAdapter
|
||||
nodeID: string | null
|
||||
): Ref<ChildrenResult<RESTCollectionViewItem>> {
|
||||
return computed(() => {
|
||||
const roots = this.data.value as unknown as SearchTreeNode[]
|
||||
|
||||
if (nodeID === null) {
|
||||
return {
|
||||
status: "loaded" as const,
|
||||
data: this.data.value.map((item, index) => ({
|
||||
data: roots.map((item, index) => ({
|
||||
id: index.toString(),
|
||||
|
||||
data: <RESTCollectionViewItem>{
|
||||
type: "collection",
|
||||
value: {
|
||||
collectionID: index.toString(),
|
||||
isLastItem: index === this.data.value.length - 1,
|
||||
name: item.name,
|
||||
isLastItem: index === roots.length - 1,
|
||||
name: getNodeName(item),
|
||||
parentCollectionID: null,
|
||||
},
|
||||
},
|
||||
@ -33,11 +65,11 @@ export class WorkspaceRESTSearchCollectionTreeAdapter
|
||||
}
|
||||
|
||||
const indexPath = nodeID.split("/").map((x) => parseInt(x, 10))
|
||||
|
||||
const item = navigateToFolderWithIndexPath(this.data.value, indexPath)
|
||||
const item = navigateToNode(roots, indexPath)
|
||||
|
||||
if (item) {
|
||||
const collections = item.folders.map(
|
||||
const childCollections = getChildCollections(item)
|
||||
const collections = childCollections.map(
|
||||
(childCollection, childCollectionID) => {
|
||||
return {
|
||||
id: `${nodeID}/${childCollectionID}`,
|
||||
@ -45,9 +77,9 @@ export class WorkspaceRESTSearchCollectionTreeAdapter
|
||||
type: "collection",
|
||||
value: {
|
||||
isLastItem:
|
||||
childCollectionID === item.folders.length - 1,
|
||||
childCollectionID === childCollections.length - 1,
|
||||
collectionID: `${nodeID}/${childCollectionID}`,
|
||||
name: childCollection.name,
|
||||
name: getNodeName(childCollection),
|
||||
parentCollectionID: nodeID,
|
||||
},
|
||||
},
|
||||
@ -55,14 +87,14 @@ export class WorkspaceRESTSearchCollectionTreeAdapter
|
||||
}
|
||||
)
|
||||
|
||||
const requests = item.requests.map((request, requestID) => {
|
||||
const requestsList = item.requests ?? []
|
||||
const requests = requestsList.map((request, requestID) => {
|
||||
return {
|
||||
id: `${nodeID}/${requestID}`,
|
||||
data: <RESTCollectionViewItem>{
|
||||
type: "request",
|
||||
value: {
|
||||
isLastItem:
|
||||
requestID === item.requests.length - 1,
|
||||
isLastItem: requestID === requestsList.length - 1,
|
||||
parentCollectionID: nodeID,
|
||||
collectionID: nodeID,
|
||||
requestID: `${nodeID}/${requestID}`,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user