Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
- 脑图内核,并不依赖于任何UI框架,只实现逻辑以及链接渲染 - 内核不负责节点渲染 - node **`sizeof`** 函数为用户需要**重点注意以及实现的点** - 内核直接使用比较复杂,可使用UI框架封装版本 - React 框架:`bade-mind-react`
sizeof
函数为用户需要重点注意以及实现的点bade-mind-react
npm install bade-mind
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>bade-mind demo</title>
<style>
body,#root{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#container{
position: relative;
width: fit-content;
height: fit-content;
}
#node-container{
position: absolute;
left: 0;
top: 0;
}
/* 定义链接线样式 */
.mind__lines{
stroke: #2775b6;
stroke-width: 3px;
}
.node{
position: absolute;
left: 0;
top: 0;
}
.node-content{
background: #57c3c2;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.root-node{
color: white;
background: #440e25;
}
</style>
</head>
<body>
<div id="root">
<div id="container">
<div id="node-container"></div>
</div>
</div>
<script src="./index.js" type="module"></script>
</body>
</html>
import { Mind } from 'bade-mind'
const generateRoot = () => ({
negative: [
{
children: [
{
id: 'n-2-l',
sizeof: () => ({
height: 25,
width: 50
})
}
],
id: 'n-1-w-c',
sizeof: () => ({
height: 25,
width: 50
})
},
{
id: 'n-1-l',
sizeof: () => ({
height: 50,
width: 100
})
}
],
node: {
id: 'root',
sizeof: () => ({
width: 100,
height: 100
})
},
positive: [
{
children: [
{
id: 'p-2-l',
sizeof: () => ({
height: 25,
width: 50
})
}
],
id: 'p-1-w-c',
sizeof: () => ({
height: 25,
width: 50
})
},
{
id: 'p-1-l',
sizeof: () => ({
height: 50,
width: 100
})
}
]
})
window.onload = () => {
const viewport = document.getElementById('root')
const container = document.getElementById('container')
const nodeContainer = document.getElementById('node-container')
let graphic = undefined
const root = generateRoot(viewport)
graphic = new Mind.Graphic(viewport, container, {
callback: {
onNodeVisibleChange: (nodes) => {
nodeContainer.innerHTML = nodes
.map((node) => {
const anchor = graphic.getNodeAnchorCoordinate(node.id)
let content = ''
const isRoot = node.id === root.node.id
content = `
<div class="${isRoot ? 'root-node' : ''} node-content"
style="
width: ${node.sizeof().width}px;
height: ${node.sizeof().height}px;
">
${isRoot ? 'root' : ''}
</div>`
return `<div class="node" style="transform: translateX(${anchor.x}px) translateY(${anchor.y}px)">${content}</div>`
})
.join('')
}
},
childAlignMode: Mind.ChildAlignMode.structured
})
graphic.setData(root)
}
首先需要自定义实现每一个节点中的sizeof
函数,内核会在计算各节点图层位置时调用此函数获取节点尺寸
sizeof
函数需要耗费大量计算资源,则需要外部自行使用缓存等方案,内核将不会缓存sizeof
结果准备两个dom作为容器
viewport
为屏幕可视区域,内核将会监听其尺寸执行区域渲染操作
container
为渲染容器,
需要将节点渲染于其中
内核将会自动注入svg
标签(链接线)作为其直系子代
可通过onNodeVisibleChange
事件直接获取可视的节点用作后续渲染
也可以onNodeVisibleChange
事件驱动重新渲染,通过judgeNodeVisible
判断每一个节点是否可视,然后渲染
内核交由节点渲染部分给用户自行渲染
getNodeAnchorCoordinate
用作获取节点渲染锚点坐标(左上角)
建议所有节点设置position:absolute;top:0;left:0;
,然后使用transform
做坐标偏移(避免出现渲染错误的情况)
使用setData
函数设置/更新数据,启动重渲染
使用setOptions
函数设置/更新配置信息,如果需要重渲染,则需要手动调用setData
脑图绘制控制类
constructor(
viewport: HTMLElement,
container: HTMLElement,
options?: Mind.Options
)
param viewport
视窗(可视区域)
param container
容器
svg
链接线将会作为一个svg tag
自动注入到container
中
位移、脑图绘制尺寸将会自动注入到container
中
container
width
、height
、transform
、transformOrigin
属性param options
配置参数
获取节点定位锚点(左上角)位置,可在节点绘制的时候确定其位置
position:absolute;left:0;top:0;
并且配合transform
来定位,避免出现绘制异常function getNodeAnchorCoordinate(id: string): Mind.Coordinate | undefined
id
节点对应id注销事件绑定
function unbind(): void
判断节点是否可视
function judgeNodeVisible(id: string): boolean
id
节点对应id设定 options
options
需要重新计算布局等操作,推荐使用 setData
驱动数据重渲染function setOptions(options?: Mind.Options, isMerge: boolean = false): voi
options
设定选项isMerge
是否与之前的options
做合并操作生成拖动控制器
根节点不可拖拽
当前暂时只有Mind.ChildAlignMode.structured
布局算法支持拖拽功能
function dragControllerBuilder(drag: Mind.Node | string): Drag | undefined
@param drag
拖动节点node
对象或id
@return
当root(没有调用setData
)不存在时,或者drag
为根节点时,返回undefined
正常情况返回 Drag
类对象
获取渲染层尺寸
function getLayoutSize(): Mind.Size | undefined
获取id
对应节点
function getNode(id: string): Mind.Node | undefined
id
节点id获取id
对应父节点
function getParent(id: string): Mind.Node | undefined
@param id
节点id
渲染链接到某个container
下
function connectTo(container: HTMLElement): void
获取id
对应节点渲染方位
function getNodeOrientation(id: string): Mind.Orientation | undefined
@param id
节点id
主动设置位移缩放
会与之前的transform
做深度合并
请注意:setTransform
之后 onTransformChange
事件依旧会触发
此方法不受 zoomExtent.translate
、zoomExtent.scale
限制,使用需谨慎
function setTransform(transform: Partial<Mind.Transform>,duration?: number): void
@param transform
位移缩放数据
@param duration
周期,如果配置,则执行变换会附带动画效果
设定位移
zoomExtent.translate
限制function translate(translate: Mind.Coordinate,duration?: number): void
@param translate
位移差(屏幕尺度)
@param duration
周期,如果配置,则执行变换会附带动画效果
设定缩放
zoomExtent.translate
限制zoomExtent.scale
限制function scale(
scale: number,
point?: Mind.Coordinate,
duration?: number): void
@param scale
缩放比
@param point
缩放相对点(如不配置或为undefined
,则默认相对于viewport
中心缩放)
@param duration
动画周期,如配置,则位移会附带动画效果
将某一个节点中心从某个相对位置做位移(其尺度为屏幕尺度)操作
zoomExtent.translate
限制function nodeTranslateTo(
config: {id: string, diff: Mind.Coordinate, relative: Mind.Relative},
duration?: number): void
config
配置参数config.id
节点idconfig.diff
位移差config.relative
相对位置duration
动画周期,如配置,则位移会附带动画效果获取位移缩放信息
function getTransform(): Mind.Transform
设置锚点节点
function setAnchor(id?: string): void
id
锚定节点id(如不设定,则清空锚点,根节点居中,缩放比归一)设置/更新数据,启动重渲染
anchor
对应节点在屏幕上的相对位置不变anchor
没有设定,或者找不到对应节点,则,根节点居中,缩放比重置为1function setData(root: Mind.Root): void
root
根数据刷新布局,启动重渲染
其用法与setData
一致,使用的是内部存储的数据
拖动逻辑相关控制类,实现拖拽计算逻辑,不与特定手势关联
Graphic.dragControllerBuilder
生成,自动注入所需数据,不推荐手动new
初始化对象constructor(context: {
options: Required<Mind.Options>,
cacheMap: Mind.CacheMap,
root: Mind.Root,
dragNode: Mind.Node,
container: HTMLElement
})
获取拖动节点插入到关联节点子代的下标
function calcDropIndex(
attachedNodeChildren: BadeMind.Node[] | undefined,
dropPosition: BadeMind.Coordinate,
dragNode: BadeMind.Node,
attachedNode: BadeMind.Node): number
param attachedNodeChildren
关联节点的子代
param dropPosition
拖动节点镜像中心位置
param dragNode
拖动节点
param attachedNode
被关联的节点
return 期望插入位置
如果父级改变,则为期望插入位置下标,直接插入子代中即可
如果父级未变,则需要先使用下标插入到对应位置之后,删除原先的节点
通知控制器正在执行拖动操作,计算链接信息
function drag(position: BadeMind.Coordinate,canBeAttachedNodes: BadeMind.Node[]): {
orientation: "negative" | "positive",
attach: BadeMind.Node
} | undefined
param position
拖动节点镜像中心位置
param canBeAttachedNodes
需要搜索的可关联节点
return 链接关联信息
如没有合法的链接节点,则返回undefined
orientation
代表拖拽节点链接到目标节点的相对区域
attach
为拖拽节点依附的目标节点
通知控制器拖动操作结束
function end(): void
export interface Options {
/**
* 渲染方向
* - positive 在 x 模式下渲染在右侧,y 模式下渲染在上侧
* - negative 在 x 模式下渲染在左侧,y 模式下渲染在下侧
* @default 'x' 水平方向
*/
direction?: Direction
/**
* 节点间距
* @default 50
*/
nodeSeparate?: number
/**
* 每一级的距离
* @default 50
*/
rankSeparate?: number
/**
* 子代对齐模式(布局模式)
* @default 'structured'
*/
childAlignMode?: ChildAlignMode
/**
* 视窗四周预加载尺寸
* @default 0
*/
viewportPreloadPadding?: number
/**
* 回调
*/
callback?: Callback
/**
* 事件
*/
event?: Event
/**
* 连线样式风格
* @default 'bezier'
*/
lineStyle?: LinkStyle
/**
* 自定义path渲染路径
* - 优先级高于 `lineStyle`
* @param data
* @return 返回字符串将作为 path d 属性
*/
pathRender?: PathRender | undefined
/**
* 缩放尺度控制
*/
zoomExtent?: ZoomExtent
/**
* 自定义布局处理器
* - 优先级高于 childAlignMode 选择的布局方式
*/
layoutProcess?: { new (): Process.Lifecycle }
}
根节点
positive
、negative
interface Root {
/**
* 根节点数据
*/
node: Omit<Node, 'children'>
/**
* 正向区域节点
*/
positive?: Node[]
/**
* 负向区域节点
*/
negative?: Node[]
}
节点信息
interface Node {
/**
* 获取当前节点尺寸
*/
sizeof: () => Size
/**
* 全局唯一 id
*/
id: string
/**
* 子代
*/
children?: Node[]
/**
* 是否折叠子代
* - 根节点为数组,[negative,positive]
* - 普通节点直接代表是否折叠子代
* @default false | [false,false]
*/
fold?: boolean | boolean[]
/**
* 附带数据
* - 请将节点附带的数据全部存储到此处
*/
attachData?: any
}
回调集合
interface Callback {
/**
* 转换发生改变,通知外部
* @param transform
*/
onTransformChange?: (transform: Transform) => void
/**
* 可见节点发生了改变
* - 每一次 `setData` 后都必定会调用此事件
* @param nodes 可见节点数组(节点都是对`setData`中节点数据的引用,请注意根节点设置`children`无效)
*/
onNodeVisibleChange?: (nodes: BadeMind.Node[]) => void
}
通知外部transform
相关信息发生了改变,常用于辅助额外控制行为,举个🌰:实现滚动条、缩放器等辅助控制
可见节点发生改变
nodes
中节点皆为setData root
中的数据引用
请注意对根节点的特殊处理(根节点设置children
无效,应该设置root
的positive
或negative
)
内核事件
interface Event {
/**
* 视窗上下文菜单事件
* - 组件禁用了在视窗上的右键菜单
* @param e
*/
onViewportContextMenu?: (e: MouseEvent) => void
/**
* zoom 事件触发器
*/
onZoomEventTrigger?: ZoomEvent
}
viewport
右键上下文事件触发,可通过此事件自定义右键菜单
viewport
的右键菜单事件缩放位移相关按钮手势事件触发
缩放事件
/**
* 位移/缩放事件函数
* @param event the underlying input event, such as mousemove or touchmove
*/
type ZoomEventFunc = (event: any) => void
interface ZoomEvent {
/**
* 缩放/拖动开始事件
*/
start?: ZoomEventFunc
/**
* 缩放/拖动中事件
*/
zoom?: ZoomEventFunc
/**
* 缩放/拖动结束
*/
end?: ZoomEventFunc
}
缩放、位移边界设定
interface ZoomExtent {
/**
* 位移边界
* - 其是可视区域(viewport)在图形所在世界的边界坐标
* - 计算时,可以简单的将 viewport 视作可移动的部分,图形保持位置不变(注意scale带来的影响,需要将viewport转换到图形所在世界坐标系,EM: viewport.width/scale))
* @default [[x: -∞, y: -∞], [x: +∞, y :+∞]]
*/
translate?: [Coordinate, Coordinate]
/**
* 缩放边界
* @default [0, ∞]
*/
scale?: [number, number]
}
内核预设链接风格
type LinkStyle = "line" | "bezier"
bezier
贝塞尔曲线链接line
线性链接
ChildAlignMode.structured
风格下表现最佳尺寸
interface Size {
width: number
height: number
}
坐标
interface Coordinate {
x: number
y: number
}
渲染方向
positive
在 x 模式下渲染在右侧,y 模式下渲染在上侧
negative
在 x 模式下渲染在左侧,y 模式下渲染在下侧
type Direction = 'x' | 'y'
x
横向渲染模式
y
纵向渲染模式
内核内置布局方式
type ChildAlignMode = "heir-center" | "structured" | "descendant-center"
descendant-center
子代对齐模式(同一父节点的子代视作整体对齐)heir-center
直系子代对齐模式(同一父节点直系子代对齐)structured
结构化规整模式(同一父节点直系子代边缘对齐)渲染区域位移缩放转化信息
interface Transform {
x: number
y: number
scale: number
}
相对位置
type RelativeX = "middle" | "left" | "right"
type RelativeY = "middle" | "top" | "bottom"
interface Relative {
x: RelativeX
y: RelativeY
}
自定义路径渲染器,其返回值将作为链接线path
的d
属性值
type PathRender = (context: PathRenderContext) => string
🌰:把所有节点用直线链接起来
const linePathRender: PathRender = (context) => {
const { source, target } = context
return `M${source.x},${source.y}L${target.x},${target.y}`
}
interface PathData extends Line {
/**
* 节点自身数据
*/
node: Node
}
interface PathRenderContext extends PathData {
/**
* 设定
*/
options: Required<Options>
/**
* 缓存地图
*/
cacheMap: CacheMap
}
连线信息
interface Line {
/**
* 链接线起点(节点父级)
*/
source: Coordinate
/**
* 连接线终点(节点自身)
*/
target: Coordinate
}
节点与根节点之间的位置关系
type Orientation = "negative" | "root" | "positive"
negative
节点位于根负向区域
positive
节点位于根正向区域
root
节点为根节点
节点可见信息
interface Visible {
/**
* 节点本身是否可见
*/
node: boolean
/**
* 与父级之间的连线
*/
lineAttachParent: boolean
}
内核缓存信息
type CacheMap = Map<string, NodeCache>
节点缓存信息
interface NodeCache {
/**
* 节点方位
*/
orientation: Orientation
/**
* 节点数据
*/
node: Node
/**
* 节点所属矩形大小以及位置
*/
rect: Size & Coordinate
/**
* 是否可见
*/
visible: Visible
/**
* 父级节点
*/
parent?: Node
/**
* 处理器在处理时的缓存数据
*/
processCache: any
/**
* 连线相关信息
*/
line: Line
/**
* 整体布局尺寸
* - 只有根节点保存此数据
*/
layoutSize?: Size
}
可拖拽布局类
layoutProcess
需要继承此类,并且实现calcDragAttach
、calcDropIndex
静态方法才可正常使用拖拽功能class DraggableLayout {
/**
* 计算拖动关联信息
* @param context 上下文
* @param context.cacheMap 缓存地图
* @param context.draggingRect 正在拖动节点的大小以及位置
* @param context.canBeAttachedNodes 可以被关联的节点
* @param context.ignoreNodes 需要被忽略的节点
* @param context.root 根节点
* @param context.options 选项
* @return 如果没有合法的节点关联,则返回`undefined`
*/
public static calcDragAttach = (context: {
cacheMap: CacheMap
draggingRect: Coordinate & Size
canBeAttachedNodes: Node[]
ignoreNodes: Node[]
root: Root
options: Required<Options>
}): DragAttach | undefined => {
...
}
/**
* 计算拖动结束被放置的下标
* @param context 上下文
* @param context.cacheMap 缓存地图
* @param context.attachedNodeChildren 关联节点子代
* @param context.dropPosition 拖拽结束位置
* @oaram context.Node 拖拽节点
*/
public static calcDropIndex = (context: {
cacheMap: CacheMap
attachedNodeChildren: Node[] | undefined
dropPosition: Coordinate
attachedNode: Node
dragNode: Node
root: Root
options: Options
}): number => {
...
}
/**
* 是否为合法的继承了这个类的类对象
* @param classObject
*/
public static isValidExtendsClass = (classObject: any) => {
...
}
}
处理器为拓展自定义功能
interface Lifecycle<S = void, E = void, AE = void, END = void> {
/**
* 开始步骤
*/
start?: (context: StartContext) => S
/**
* 每一个节点处理(开始深度优先递归子代之前)
* @param context 上下文环境
*/
every?: (context: EveryContext) => E
/**
* 每一个节点处理(结束深度优先递归子代之后)
* @param context 上下文环境
*/
afterEvery?: (context: EveryContext) => AE
/**
* 结束处理步骤
*/
end?: () => END
}
开始处理节点之前的上下文
interface StartContext {
/**
* 配置项
*/
options: Required<Options>
/**
* 根数据
*/
root: Root
/**
* 获取根直系子代的方位
* @param id 直系子代 id
*/
getRootHeirOrientation: (id: string) => Orientation
/**
* 缓存地图
*/
cacheMap: CacheMap
/**
* 上一次的缓存地图
*/
preCacheMap?: CacheMap
/**
* 可视窗口
*/
viewport: HTMLElement
/**
* 内容容器
*/
container: HTMLElement
/**
* 位移/缩放配置
*/
transform: Transform
/**
* 配置锚点
*/
anchor?: string
/**
* 位移/缩放控制器
*/
zoom: Zoom
}
处理每一个节点的上下文
interface EveryContext {
/**
* 当前处理节点缓存信息
*/
cache: NodeCache
/**
* 是否折叠子代
* - 根节点为数组,[negative,positive]
* - 普通节点直接代表是否折叠子代
* @default false | [false,false]
*/
fold?: boolean | boolean[]
}
FAQs
- 脑图内核,并不依赖于任何UI框架,只实现逻辑以及链接渲染 - 内核不负责节点渲染 - node **`sizeof`** 函数为用户需要**重点注意以及实现的点** - 内核直接使用比较复杂,可使用UI框架封装版本 - React 框架:`bade-mind-react`
The npm package bade-mind receives a total of 2 weekly downloads. As such, bade-mind popularity was classified as not popular.
We found that bade-mind demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.