npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@tc-utils/mc-tree

v1.1.0

Published

## 安装

Downloads

5

Readme

@tc-utils/mc-tree

安装

使用 npm:

$ npm i -g npm
$ npm i @tc-utils/mc-tree

使用方法

直接引用 import console from '@tc-utils/mc-tree'

import Tree, { TreeActionType, TreeProps } from '@tc-utils/mc-tree'
import console from '@tc-utils/consoles'
import {
  Button,
  DotLoading,
  SearchBar,
  Space,
  Toast,
  Checkbox,
  Radio,
} from 'antd-mobile'
import { TeamOutline, UserOutline } from 'antd-mobile-icons'
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
import './demo.less'
import {
  initTreeData,
  mockRequest,
  mockSearchRequest,
  updateTreeData,
} from './utils'

const App = () => {
  const actionRef = useRef<TreeActionType>()
  const [expandedKeys, setExpandedKeys] = useState<TreeProps['expandedKeys']>(
    []
  )
  const [checkedKeys, setCheckedKeys] = useState<TreeProps['checkedKeys']>([
    'node_0',
  ])
  const [selectedKeys, setSelectedKeys] = useState<TreeProps['selectedKeys']>([
    'node_0',
  ])
  const [treeData, setTreeData] = useState(initTreeData)
  const [loading, setLoading] = useState<boolean>(false)
  const [checkedAll, setCheckedAll] = useState<boolean>(false)
  const ref = useRef<HTMLDivElement>()

  const handlerReloadNode = () => {
    actionRef.current?.reloadNode()
  }

  const handlerGoBackNode = () => {
    actionRef.current?.goBackNode()
  }

  const handlerGoBackPointNode = () => {
    actionRef.current?.goBackPointNode('node_0')
  }

  const handlerCheckAllNode = () => {
    console.log('🚀🚀 ~ handlerCheckAllNode ~ expandedKeys', expandedKeys)
    actionRef.current?.checkedAll(!checkedAll)
    setCheckedAll(!checkedAll)
  }

  const handlerEmptySelectedNode = () => {
    console.log('🚀🚀 ~ handlerEmptySelectedNode')
    setCheckedKeys([])
    setSelectedKeys([])
  }

  const handlerShowNodePath = () => {
    console.log('🚀🚀 ~ handlerShowNodePath ~ expandedKeys', expandedKeys)
    const entities = actionRef.current?.getKeyEntities(expandedKeys)
    console.log('🚀🚀 ~ handlerShowNodePath ~ entities', entities)
  }

  const handlerScrollToTop = () => {
    actionRef.current?.scrollTo(0)
  }

  const onExpand: TreeProps['onExpand'] = (expandedKeysValue, info) => {
    console.log('🚀🚀 ~ onExpand expandedKeysValue', expandedKeysValue)
    console.log('🚀🚀 ~ onExpand info', info)
    setExpandedKeys(expandedKeysValue)
  }

  const onSelect: TreeProps['onSelect'] = (checkedKeysValue, info) => {
    console.log('🚀🚀 ~ info', info)
    console.log('🚀🚀 ~ checked', checkedKeysValue)
    setSelectedKeys(checkedKeysValue)
  }

  const onCheck: TreeProps['onCheck'] = (checkedKeysValue, info) => {
    console.log('🚀🚀 ~ info', info)
    console.log('🚀🚀 ~ checked', checkedKeysValue)
    setCheckedKeys(checkedKeysValue)
  }

  const onCheckAll: TreeProps['onCheckAll'] = (checkedKeysValue, info) => {
    console.log('🚀🚀 ~ info', info)
    console.log('🚀🚀 ~ onCheckAll', checkedKeysValue)
    setCheckedKeys(checkedKeysValue)
  }

  const onBack: TreeProps['onBack'] = (expandedKeysValue, info) => {
    console.log('🚀🚀 ~ onBack expandedKeysValue', expandedKeysValue)
    console.log('🚀🚀 ~ onBack info', info)
    setExpandedKeys(expandedKeysValue)
  }

  useEffect(() => {
    console.log('🚀🚀 ~ checkedKeys', checkedKeys)
  }, [checkedKeys])

  return (
    <div className="demo-wrapper">
      <div className="demo-searchbar">
        <SearchBar
          placeholder="请输入内容"
          defaultValue="node_0_0"
          onSearch={async (keyword) => {
            setLoading(true)
            if (keyword) {
              const result = await mockSearchRequest(keyword)
              console.log('🚀🚀 ~ result', result)
              setTreeData(result)
            } else {
              setTreeData(initTreeData)
            }
            setLoading(false)
          }}
        />
      </div>
      <div className="demo-toolbar">
        <Space wrap>
          <Button color="primary" onClick={() => handlerReloadNode()}>
            重置节点
          </Button>
          <Button color="primary" onClick={() => handlerGoBackNode()}>
            返回节点
          </Button>
          <Button color="primary" onClick={() => handlerGoBackPointNode()}>
            返回指定节点
          </Button>
          <Button color="primary" onClick={() => handlerCheckAllNode()}>
            {checkedAll ? '全不选' : '全选'}
          </Button>
          <Button color="primary" onClick={() => handlerEmptySelectedNode()}>
            清空全部选中
          </Button>
          <Button color="primary" onClick={() => handlerShowNodePath()}>
            节点路径
          </Button>
          <Button color="primary" onClick={() => handlerScrollToTop()}>
            回到顶部
          </Button>
        </Space>
      </div>
      <div
        className="demo-toolbar"
        style={{
          display: loading ? 'block' : 'none',
        }}
      >
        <DotLoading color="primary" /> 数据加载中
      </div>
      <div className="demo-tree" ref={ref}>
        <Tree
          treeData={treeData}
          loadData={async (node, props) => {
            setLoading(true)
            const children = await mockRequest(node)
            // const newTreeData = [...treeData, ...children]
            // setTreeData(newTreeData)
            setTreeData((origin) => updateTreeData(origin, node.key, children))
            setLoading(false)
          }}
          actionRef={actionRef}
          // virtualRoot={{
          //   key: 'root',
          //   title: '根节点',
          //   icon: 'Ico',
          //   switcherIcon: '下级',
          // }}
          expandAction="click"
          onlySelectLeaf
          // showLeafIcon={false}
          expandedKeys={expandedKeys}
          checkedKeys={checkedKeys}
          // multiple={false}
          selectedKeys={selectedKeys}
          // checkStrictly={false}
          // maxCount={2}
          icon={(props) => {
            if (props.isLeaf) {
              return <UserOutline />
            }
            return <TeamOutline />
          }}
          switcherIcon={() => {
            return '下级'
          }}
          onOverMaxCount={(maxCount, info) => {
            console.log('🚀🚀 ~ App ~ info', info)
            Toast.show(`${maxCount}`)
          }}
          onExpand={onExpand}
          onSelect={onSelect}
          onCheck={onCheck}
          onCheckAll={onCheckAll}
          onBack={onBack}
          style={{
            height: '100%',
          }}
          renderCheckbox={(info) => {
            return (
              <Checkbox
                checked={info.checked}
                value={info.value}
                disabled={info.disabled}
                indeterminate={info.halfChecked}
              />
            )
          }}
          renderRadio={(info) => {
            return (
              <Radio
                checked={info.checked}
                value={info.value}
                disabled={info.disabled}
              />
            )
          }}
        />
      </div>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

demo.less 文件

html,
body,
#root {
  height: 100%;
  padding: 0;
  margin: 0;
}

.demo-wrapper {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100vh;

  .demo-searchbar {
    flex: 0 0 auto;
    padding: 10px;
  }

  .demo-toolbar {
    flex: 0 0 auto;
    padding: 10px;
  }

  .demo-tree {
    position: relative;
    flex: 1 1 auto;
    overflow: auto;
    padding: 10px;
    font-size: 18px;
  }
}

::-webkit-scrollbar-track-piece {
  background-color: transparent;
}

::-webkit-scrollbar {
  width: 3px;
  height: 10px;
}

::-webkit-scrollbar-thumb {
  height: 50px;
  background-color: #b8b8b8;
  border-radius: 6px;
  outline: 2px solid #fff;
  outline-offset: -2px;
  filter: alpha(opacity=50);
  -moz-opacity: 0.5;
  -khtml-opacity: 0.5;
  opacity: 0.5;
}

::-webkit-scrollbar-thumb:hover {
  height: 50px;
  background-color: #878987;
  border-radius: 6px;
}

* {
  scrollbar-color: #b8b8b8 transparent;
  scrollbar-width: thin;
}

utils.ts

import { TreeDataNode, TreeEventDataNode, TreeNodeKey } from '@tc-utils/mc-tree'
// import { httpRequest } from "../../demos/utils";

const TREE_LENGTH = 20
const TREE_LEVEL = 4

const sleep = (time: number) =>
  new Promise((resolve) => setTimeout(resolve, time))

const genTreeNode = (
  index: number,
  length: number,
  level: number,
  parentId: TreeNodeKey
) => {
  const itemId = `${parentId ?? 'node'}_${index}`
  let item: TreeDataNode = {
    key: itemId,
    title: `文件_${itemId}`,
    level,
    parentId,
    description: `描述内容`,
    // icon: 'Ico',
    // switcherIcon: '下级',
  }
  if (level !== 0 && index < length / 3) {
    // item.children = genStaticTreeData(length, itemId, level - 1);
    // item.selectable = false;
    // item.disabled = true;
    // item.checkable = false;
    // item.disableCheckbox = true;
    item.title = `文件夹_${itemId}(${level ?? 0} Level)`
  } else {
    item.disabled = index === 6
    item.isLeaf = true
  }
  return item
}

// It's just a simple demo. You can use tree map to optimize update perf.
export const updateTreeData = (
  list: TreeDataNode[],
  key: TreeNodeKey,
  children: TreeDataNode[]
): TreeDataNode[] =>
  list.map((node) => {
    if (node.key === key) {
      return {
        ...node,
        children,
      }
    }
    if (node.children) {
      return {
        ...node,
        children: updateTreeData(node.children, key, children),
      }
    }
    return node
  })

const genStaticTreeData = (
  length: number,
  level = 2,
  parentId?: TreeNodeKey
) => {
  return Array.from({ length }, (val, index) => {
    const item = genTreeNode(index, length, level, parentId)
    if (!item.isLeaf) {
      item.children = genStaticTreeData(length, level - 1, item.key)
    }
    return item
  })
}

const genLazyTreeData = (length: number, level = 2, parentId?: TreeNodeKey) => {
  return Array.from({ length }, (val, index) => {
    const item = genTreeNode(index, length, level, parentId)
    return item
  })
}

export const treeData = genStaticTreeData(TREE_LENGTH, TREE_LEVEL)
export const initTreeData = genLazyTreeData(TREE_LENGTH, TREE_LEVEL)

const buildTreeSelectedNodes = (keys: TreeNodeKey[]): TreeDataNode[] => {
  console.log('🚀🚀 ~ file: utils.ts ~ line 109 ~ keys', keys)
  return keys.map((key) => {
    const parentKey = `${key}`.match(/(\S*)_/)[1]
    return {
      key: key,
      title: `默认选中节点${key}`,
      level: 2,
      parentKey,
      isLeaf: true,
    }
  })
}

export const mockRequest = async (
  treeNode: TreeEventDataNode
): Promise<TreeDataNode[] | null | undefined> => {
  await sleep(1000)
  console.group('🚀🚀 Mock Request')
  console.log('🚀🚀 ~ file: utils.ts ~ line 105 ~ treeNode', treeNode)
  // console.log("🚀🚀 ~ file: utils.ts ~ line 105 ~ props", props)
  let childrenData: TreeDataNode[] | null = null
  const level = treeNode?.level ?? 0
  if (level > 0) {
    childrenData = genLazyTreeData(
      Math.round(Math.random() * 100 + 1),
      level - 1,
      treeNode.key
    )
  }
  console.log('🚀🚀 ~ childrenData', childrenData)
  console.groupEnd()
  return Promise.resolve(childrenData)
}

export const mockNodesRequest = async (
  keys: TreeNodeKey[]
): Promise<TreeDataNode[] | null | undefined> => {
  await sleep(5000)
  console.group('🚀🚀 Mock Nodes Request')
  console.log('🚀🚀 ~ file: utils.ts ~ line 105 ~ keys', keys)
  // console.log("🚀🚀 ~ file: utils.ts ~ line 105 ~ props", props)
  let nodeItems: TreeDataNode[] = buildTreeSelectedNodes(keys)
  console.groupEnd()
  return Promise.resolve(nodeItems)
}

// 字符串包含
const isInclude = (str: string, matchStr: string) => {
  if (matchStr) {
    return str.toLowerCase().indexOf(matchStr.toLowerCase()) >= 0
  }
  return true
}

const findTreeNodeByKeyWord = (treeNodes: TreeDataNode[], keyWord: string) => {
  const nodes: TreeDataNode[] = []
  if (treeNodes) {
    treeNodes.forEach(({ children, ...treeNode }) => {
      if (isInclude(treeNode.title as string, keyWord)) {
        nodes.push(treeNode)
      }
      if (children) {
        const childNodes = findTreeNodeByKeyWord(children, keyWord)
        nodes.push(...childNodes)
      }
    })
  }

  return nodes
}

export const mockSearchRequest = async (keyWord: string) => {
  let tree = genStaticTreeData(TREE_LENGTH, TREE_LEVEL)
  const searchNodes = findTreeNodeByKeyWord(tree, keyWord)

  return searchNodes
}