UNPKG

@blocklet/ui-react

Version:

Some useful front-end web components that can be used in Blocklets.

180 lines (156 loc) 5.57 kB
import semver from 'semver'; export const mapRecursive = (array, fn, childrenKey = 'children') => { return array.map((item) => { if (Array.isArray(item[childrenKey])) { return fn({ ...item, [childrenKey]: mapRecursive(item[childrenKey], fn, childrenKey), }); } return fn(item); }); }; // 展平有层级结构的 array export const flatRecursive = (array, childrenKey = 'children') => { const result = []; mapRecursive(array, (item) => result.push(item), childrenKey); return result; }; // 对有层级结构的 array 元素计数 export const countRecursive = (array, childrenKey = 'children') => { let counter = 0; mapRecursive(array, () => counter++, childrenKey); return counter; }; // 对有层级结构的 array 进行 filter 处理 // 因为是 DFS 遍历, 可以借助 context.filteredChildren 在过滤/保留子结的同时保持父子结构 (即使父结点不满足筛选条件) export const filterRecursive = (array, predicate, childrenKey = 'children') => { return array .map((item) => ({ ...item })) .filter((item) => { const children = item[childrenKey]; if (Array.isArray(children)) { const filtered = filterRecursive(children, predicate, childrenKey); item[childrenKey] = filtered?.length ? filtered : undefined; } const context = { filteredChildren: item[childrenKey], isLeaf: !children?.length }; return predicate(item, context); }); }; // "http://", "https://" 2 种情况 export const isUrl = (str) => { return /^https?:\/\//.test(str); }; /** * @description 检测是否是 Iconify 格式的字符串 * @deprecated */ export const isIconifyString = (str) => { return /^[\w-]+:[\w-]+$/.test(str); }; /** * 检测 path 是否匹配当前 location, path 只考虑 "/" 开头的相对路径 */ export const matchPath = (path) => { if (!path || !path?.startsWith('/')) { return false; } const ensureTrailingSlash = (str) => (str.endsWith('/') ? str : `${str}/`); const pathname = ensureTrailingSlash(window.location.pathname); const normalizedPath = ensureTrailingSlash(new URL(path, window.location.origin).pathname); return pathname.startsWith(normalizedPath); }; /** * 从一组 paths 中, 找到匹配当前 location 的 path, 返回序号 */ export const matchPaths = (paths = []) => { const matched = paths.map((item, index) => ({ path: item, index })).filter((item) => matchPath(item.path)); if (!matched?.length) { return -1; } // 多个 path 都匹配时, 取一个最具体 (最长的) path const mostSpecific = matched.slice(1).reduce((prev, cur) => { return prev.path.length >= cur.path.length ? prev : cur; }, matched[0]); return mostSpecific.index; }; /** 导航列表分列 */ export const splitNavColumns = (items, options = {}) => { const { columns = 1, breakInside = false, groupHeight = 48, itemHeight = 24, childrenKey = 'items' } = options; // 高度预估 const totalHeight = items.reduce((height, group) => { return height + groupHeight + (group[childrenKey]?.length || 0) * itemHeight; }, 0); const targetHeight = Math.ceil(totalHeight / columns); // 使用贪心策略进行分列 const result = [[]]; let currentColumn = 0; let currentHeight = 0; // 允许的高度偏差范围(有利于得到高度相差不大的列) const heightVariance = targetHeight * 0.2; // 是否应该分列 const shouldBreakColumn = (nextHeight) => { return ( currentHeight > targetHeight - heightVariance && currentColumn < columns - 1 && currentHeight + nextHeight > targetHeight + heightVariance ); }; items.forEach((group) => { const groupTotalHeight = groupHeight + (group[childrenKey]?.length || 0) * itemHeight; // 允许截断分组时,可以在任何子项处换列 if (breakInside && shouldBreakColumn(groupHeight)) { currentColumn++; currentHeight = 0; result[currentColumn] = []; } // 不允许截断分组时,只能在分组边界换列 if (!breakInside && currentHeight > 0 && shouldBreakColumn(groupTotalHeight)) { currentColumn++; currentHeight = 0; result[currentColumn] = []; } // 添加分组标题 result[currentColumn].push({ ...group, group: true, }); currentHeight += groupHeight; // 添加子项 if (group[childrenKey]) { group[childrenKey].forEach((child) => { if (breakInside && shouldBreakColumn(itemHeight)) { currentColumn++; currentHeight = 0; result[currentColumn] = []; } result[currentColumn].push({ ...child, group: false, }); currentHeight += itemHeight; }); } }); return result; }; /** * 比较两个版本号,用于判断版本号是否兼容 * @param {*} version1 * @param {*} version2 * @returns 0: 版本相同, -1: version1 < version2, 1: version1 > version2 */ export const compareVersions = (version1, version2) => { const getDateVersion = (version) => { const match = version.match(/^(\d+\.\d+\.\d+(?:-[^-]+?-\d{8}))/); return match ? match[1] : version; }; const dateVersion1 = getDateVersion(version1); const dateVersion2 = getDateVersion(version2); // 如果基础版本相同,但完整版本不同(意味着有额外部分),返回false if (dateVersion1 === dateVersion2 && version1 !== version2) { return false; } // 其他情况正常比较 return semver.gte(dateVersion1, dateVersion2); };