├── .gitignore ├── README.md ├── image ├── npc.gif └── test.gif ├── pom.xml └── src ├── main ├── java │ └── cn │ │ └── murfree │ │ ├── contents │ │ ├── AbortType.java │ │ └── NodeStatus.java │ │ ├── context │ │ ├── DefaultContext.java │ │ └── IContext.java │ │ ├── dto │ │ └── ConditionalReevaluate.java │ │ ├── node │ │ ├── base │ │ │ ├── IActionNode.java │ │ │ ├── IConditionalNode.java │ │ │ ├── INode.java │ │ │ ├── IParentNode.java │ │ │ └── ITree.java │ │ ├── composite │ │ │ ├── CompositeParallel.java │ │ │ ├── CompositeRandomSelevtor.java │ │ │ ├── CompositeRandomSequence.java │ │ │ ├── CompositeSelector.java │ │ │ ├── CompositeSequence.java │ │ │ └── ICompositeNode.java │ │ └── decorator │ │ │ ├── DecoratorInverter.java │ │ │ └── IDecoratorNode.java │ │ └── utils │ │ └── MurFreeBuilder.java └── resources │ └── static │ ├── css │ ├── animate.css │ ├── font-awesome.css │ └── style.css │ ├── dist │ ├── css │ │ └── jquery.orgchart.css │ └── js │ │ └── jquery.orgchart.js │ ├── index.html │ └── js │ ├── bootstrap.min.js │ └── new.js └── test └── java └── cn └── murfree ├── Main.java ├── action ├── ActionAddHp.java ├── ActionPatrol.java └── ActionSkill.java ├── conditional ├── ConditionalHp.java └── ConditionalMp.java ├── context └── TestContext.java └── tree └── TestTree.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | logs/ 4 | !.mvn/wrapper/maven-wrapper.jar 5 | !**/src/main/**/target/ 6 | !**/src/test/**/target/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | 23 | ### NetBeans ### 24 | /nbproject/private/ 25 | /nbbuild/ 26 | /dist/ 27 | /nbdist/ 28 | /.nb-gradle/ 29 | build/ 30 | !**/src/main/**/build/ 31 | !**/src/test/**/build/ 32 | 33 | ### VS Code ### 34 | .vscode/ 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MurFree 2 | 3 | 这一个基于java的可视化行为编程框架,灵感来自于行为树模式,通过分治的思想,把复杂的业务逻辑拆成一个个最简单逻辑单元,再组合成一个多叉树; 4 | 5 | *This visual behavior programming framework based on Java is inspired by the behavior tree mode. Through the idea of divide and conquer, the complex business logic is divided into the simplest logic units and then combined into a multi tree;* 6 | 7 | + 主要是为了解决: 8 | - 冗长的业务逻辑,在或没有注释的情况下,很难理解; 9 | - 高深的设计模式导致可读性较差; 10 | - 复杂多变的业务场景可视化、简单化; 11 | - 你可以用本框架轻松的构建一个属于你的用户画像模型; 12 | - 一个智能AI模型; 13 | - 一个智能的测试模型,配合你的图像识别逻辑,进行测试; 14 | - 取决于你得想象力; 15 | 16 | 17 | + *Mainly to solve:* 18 | - *Lengthy business logic is difficult to understand with or without comments;* 19 | - *Advanced design patterns lead to poor readability;* 20 | - *Visualization and simplification of complex and changeable business scenarios;* 21 | - *You can use this framework to easily build a user portrait model that belongs to you;* 22 | - *An intelligent AI model;* 23 | - *An intelligent test model, with your image recognition logic, to test;* 24 | - *It depends on your imagination;* 25 | 26 | ---- 27 | 本框架通过使用基础节点和自定义节点,构建一颗专属的业务逻辑的树,框架会以前序遍历的方式执行树。 28 | 29 | *This framework builds a dedicated business logic tree by using basic nodes and custom nodes, and the framework will execute the tree in the way of previous traversal.* 30 | 31 | + 基础节点介绍: 32 | - CompositeSequence:会顺序执行子节点,当有子节点全部执行成功,则该节点执行成功,其中一个子节点执行失败,则该节点会终止执行后续子节点,该节点变为失败状态。 33 | - CompositeRandomSequence:同上,但是不会顺序执行,会随机执行子节点。 34 | - CompositeSelector:会顺序执行子节点,当某个节点返回成功时,该节点执行成功,并中止子节点的运行,当所有子节点都执行失败时,该节点变为失败。 35 | - CompositeRandomSelevtor:同上,但是不会顺序执行,会随机执行子节点。 36 | - todo 后续会增加更多的基础节点、包装节点等 37 | 38 | 39 | + *Introduction to basic nodes:* 40 | - *Compositesequence: the sub nodes will be executed in sequence. When all the sub nodes are executed successfully, the node will be executed successfully. If one of the sub nodes fails, the node will terminate the execution of subsequent sub nodes, and the node will become a failed state.* 41 | - *Compositerandomsequence: the same as above, but it will not be executed sequentially, and the child nodes will be executed randomly.* 42 | - *Compositeselector: the child nodes will be executed in sequence. When a node returns success, the node will be executed successfully and the operation of the child nodes will be suspended. When all the child nodes fail to execute, the node will become a failure.* 43 | - *Compositerandomselevtor: the same as above, but it will not be executed in sequence, and the child nodes will be executed randomly.* 44 | - *Todo will add more basic nodes, Decorator nodes, etc* 45 | 46 | ---- 47 | 例如:我们现在需要做一个智能的测试模型: 48 | 49 | *For example, we now need to build an intelligent test model:* 50 | 51 | 当钱包里的币充足时,就浏览商品,并且随机购买,随机的进行收货、退货、评论,当钱币不足时进行充值,如果APP黑屏闪退,记录日志并且重新打开APP继续测试。 52 | 53 | *When there is enough money in the wallet, browse the goods, buy at random, receive, return and comment at random, and recharge when there is insufficient money. If the app flashes back on the black screen, record the log and reopen the app to continue the test.* 54 | 55 | 规划的树如下: 56 | 57 | ![test](https://user-images.githubusercontent.com/31950679/183277988-cd4693e4-a4ba-4be8-9baf-2cbba50ce973.gif) 58 | 59 | 例如:我们现在要做一个游戏内的NPC的AI模型,我们希望该NPC在蓝足够的时候就一直释放技能,直到Mp不足,该NPC会受伤,当他的HP不足时要立刻停止释放技能,进行回血,当MP不足并且HP充足的时候,就进行巡逻。 60 | 61 | *For example, we now want to make an AI model of an NPC in the game. We hope that the NPC will release skills when the blue is enough, until the MP is insufficient, the NPC will be injured. When his HP is insufficient, he should immediately stop releasing skills and regenerate blood. When the MP is insufficient and the HP is sufficient, he will patrol.* 62 | 63 | 规划的树如下: 64 | 65 | ![npc](https://user-images.githubusercontent.com/31950679/183277985-554b9b5a-3555-42e4-975b-9acba3a5e1d5.gif) 66 | 67 | ---- 68 | 69 | 使用工程目录下resources/static/index.html 方便的构建一颗业务树,并生成JSON数据,框架会根据反射来创建并组装节点实例。 70 | 71 | *Use resources/static/index Html is convenient to build a business tree and generate JSON data. The framework will create and assemble node instances according to reflection.* 72 | 73 | ---- 74 | 详细使用方法包含在了测试类中,开始享用吧!~ 如果这个框架帮助到了你,请给我一个start,这对我来说非常重要~谢谢~; 75 | 76 | *Detailed usage methods are included in the test class. Start to enjoy~ If this framework helps you, please give me a start, which is very important to me ~ thank you ~;* 77 | 78 | 如果你有疑问或者想一起参与该框架的迭代,请联系我:252217616@qq.com 79 | *If you have questions or want to participate in the iteration of the framework together, please contact me: 252217616@qq.com* 80 | -------------------------------------------------------------------------------- /image/npc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/252217616/murfree/cbb0c96e9c387642b9ecd796aa4d9a4b23a1f70f/image/npc.gif -------------------------------------------------------------------------------- /image/test.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/252217616/murfree/cbb0c96e9c387642b9ecd796aa4d9a4b23a1f70f/image/test.gif -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.example 6 | murfree 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | murfree 11 | http://maven.apache.org 12 | 13 | 14 | UTF-8 15 | 2.13.3 16 | 17 | 18 | 19 | 20 | junit 21 | junit 22 | 3.8.1 23 | test 24 | 25 | 26 | com.fasterxml.jackson.core 27 | jackson-databind 28 | ${jackson.version} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/cn/murfree/contents/AbortType.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.contents; 2 | 3 | /** 4 | * 中断类型 5 | * 6 | * @author luJun 7 | * @date 2022/7/22 8 | */ 9 | public enum AbortType { 10 | //不中断节点运行 11 | None, 12 | //中断低优先级的节点运行 13 | LowPriority, 14 | //中断同一组合下的节点运行 15 | Self, 16 | //同时具有LowPri 和Self 17 | Both 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/murfree/contents/NodeStatus.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.contents; 2 | 3 | /** 4 | * 节点状态 5 | * 6 | * @author luJun 7 | * @date 2022/7/22 8 | */ 9 | public enum NodeStatus { 10 | //未激活 11 | Inactive, 12 | //运行中 13 | Running, 14 | //成功 15 | Success, 16 | //失败 17 | Failure, 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/cn/murfree/context/DefaultContext.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.context; 2 | 3 | /** 4 | * DefaultContext 5 | * 6 | * @author lujun 7 | * @date 2022/8/4 8 | */ 9 | public class DefaultContext implements IContext{ 10 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/context/IContext.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.context; 2 | 3 | /** 4 | * 上下文对象 5 | * 6 | * @author luJun 7 | * @date 2022/8/4 8 | */ 9 | public interface IContext { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cn/murfree/dto/ConditionalReevaluate.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.dto; 2 | 3 | import cn.murfree.contents.NodeStatus; 4 | 5 | 6 | /** 7 | * 条件重评估 8 | * 9 | * @author lujun 10 | * @date 2022/7/22 11 | */ 12 | 13 | public class ConditionalReevaluate { 14 | /** 15 | * 当前节点的索引 16 | */ 17 | private int index; 18 | /** 19 | * 节点状态 20 | */ 21 | private NodeStatus status; 22 | /** 23 | * 父组合节点索引 24 | */ 25 | private int compositeIndex; 26 | 27 | public ConditionalReevaluate(int index, NodeStatus status, int compositeIndex) { 28 | this.index = index; 29 | this.status = status; 30 | this.compositeIndex = compositeIndex; 31 | } 32 | 33 | public int getIndex() { 34 | return index; 35 | } 36 | 37 | public void setIndex(int index) { 38 | this.index = index; 39 | } 40 | 41 | public NodeStatus getStatus() { 42 | return status; 43 | } 44 | 45 | public void setStatus(NodeStatus status) { 46 | this.status = status; 47 | } 48 | 49 | public int getCompositeIndex() { 50 | return compositeIndex; 51 | } 52 | 53 | public void setCompositeIndex(int compositeIndex) { 54 | this.compositeIndex = compositeIndex; 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/base/IActionNode.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.base; 2 | 3 | import cn.murfree.contents.NodeStatus; 4 | import cn.murfree.context.IContext; 5 | 6 | 7 | /** 8 | * 基础行动节点 9 | * 10 | * @author lujun 11 | * @date 2022/7/22 12 | */ 13 | public abstract class IActionNode extends INode { 14 | 15 | 16 | /** 17 | * 节点运行时执行 18 | */ 19 | @Override 20 | public abstract NodeStatus onUpdate(T context); 21 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/base/IConditionalNode.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.base; 2 | 3 | import cn.murfree.contents.NodeStatus; 4 | import cn.murfree.context.IContext; 5 | 6 | /** 7 | * 基础条件节点 8 | * 9 | * @author lujun 10 | * @date 2022/7/22 11 | */ 12 | public abstract class IConditionalNode extends INode { 13 | @Override 14 | public abstract NodeStatus onUpdate(T context); 15 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/base/INode.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.base; 2 | 3 | import cn.murfree.contents.NodeStatus; 4 | import cn.murfree.context.IContext; 5 | 6 | /** 7 | * INode 8 | * 节点基类 9 | * @author luJun 10 | * @date 2022/7/22 11 | */ 12 | public abstract class INode { 13 | 14 | private T context; 15 | /** 16 | * 节点名称 17 | */ 18 | private String name; 19 | 20 | /** 21 | * 节点状态 22 | */ 23 | private NodeStatus status; 24 | 25 | public NodeStatus getStatus() { 26 | return status; 27 | } 28 | 29 | public void setStatus(NodeStatus status) { 30 | this.status = status; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public void setName(String name) { 38 | this.name = name; 39 | } 40 | 41 | public INode(String name){ 42 | this.name = name; 43 | } 44 | 45 | protected T getContext() { 46 | return this.context; 47 | } 48 | 49 | public void setContext(T context) { 50 | this.context = context; 51 | } 52 | 53 | public INode(){ 54 | } 55 | 56 | /** 57 | * 节点运行前执行 58 | */ 59 | public void onStart(){ 60 | this.status = NodeStatus.Inactive; 61 | } 62 | 63 | 64 | /** 65 | * 节点运行时执行 66 | */ 67 | public NodeStatus onUpdate(T context){ 68 | return NodeStatus.Success; 69 | } 70 | 71 | /** 72 | * 节点结束运行时执行 73 | */ 74 | public void onEnd(){ 75 | this.status = NodeStatus.Inactive; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/base/IParentNode.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.base; 2 | 3 | import cn.murfree.contents.NodeStatus; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * 基础父节点 10 | * 11 | * @author lujun 12 | * @date 2022/7/22 13 | */ 14 | public abstract class IParentNode extends INode { 15 | 16 | protected List children; 17 | 18 | private int index = 0; 19 | 20 | public IParentNode(List children){ 21 | this.children = children; 22 | } 23 | 24 | public IParentNode(){ 25 | this.children = new ArrayList(); 26 | } 27 | 28 | public int getIndex() { 29 | return index; 30 | } 31 | 32 | public void setIndex(int index) { 33 | this.index = index; 34 | } 35 | 36 | 37 | /** 38 | * 该节点是否能够执行 39 | */ 40 | public abstract boolean canExecute(); 41 | 42 | /** 43 | * 子节点执行对父节点的影响 44 | */ 45 | public abstract void onChildExecuted(NodeStatus childStatus, int index); 46 | 47 | //条件中止 48 | public void onConditionalAbort(int childIndex){ 49 | } 50 | 51 | /** 52 | * 能否运行的并行节点 53 | */ 54 | public boolean canRunParallelChildren(){ 55 | return false; 56 | } 57 | 58 | /** 59 | * 并发执行时运行 60 | */ 61 | public void onChildStarted(){ 62 | 63 | } 64 | 65 | public List getChildren() { 66 | return children; 67 | } 68 | 69 | public void setChildren(List children) { 70 | this.children = children; 71 | } 72 | 73 | public void addChild(INode child){ 74 | this.children.add(child); 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/base/ITree.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.base; 2 | 3 | 4 | import cn.murfree.contents.AbortType; 5 | import cn.murfree.contents.NodeStatus; 6 | import cn.murfree.context.IContext; 7 | import cn.murfree.dto.ConditionalReevaluate; 8 | import cn.murfree.node.composite.ICompositeNode; 9 | import cn.murfree.node.decorator.IDecoratorNode; 10 | 11 | import java.util.*; 12 | 13 | /** 14 | * 基础树 15 | * 16 | * @author lujun 17 | * @date 2022/7/22 18 | */ 19 | public abstract class ITree { 20 | /** 21 | * 根节点 22 | */ 23 | private final INode root; 24 | 25 | /** 26 | * 上下文对象 27 | */ 28 | private T context; 29 | /** 30 | * 是否允许此行为树重新开始 31 | */ 32 | private boolean isCanRestart = true; 33 | 34 | /** 35 | * 通过前序遍历保存行为树所有节点 36 | */ 37 | private final List nodeList = new ArrayList(); 38 | /** 39 | * 运行栈 40 | */ 41 | private final Stack> activeStack = new Stack>(); 42 | /** 43 | * 每个节点的父节点再nodeList上的索引 44 | */ 45 | private final List parentIndex = new ArrayList(); 46 | /** 47 | * 每个节点的子元素在nodeList上的索引 48 | */ 49 | private final List> childrenIndex = new ArrayList>(); 50 | /** 51 | * 当前元素是爸爸的第几个孩子 52 | */ 53 | private final List relativeChildIndex = new ArrayList(); 54 | /** 55 | * 第一个爸爸组合节点的index 56 | */ 57 | private final List parentCompositeIndex = new ArrayList(); 58 | /** 59 | * 孩子是条件节点的索引数组 60 | */ 61 | private final List> childConditionalIndex = new ArrayList>(); 62 | /** 63 | * 条件重评估对象保存 64 | */ 65 | private final List conditionalReevaluateList = new ArrayList(); 66 | private final Map conditionalReevaluateMap = new HashMap(); 67 | 68 | public boolean isCanRestart() { 69 | return isCanRestart; 70 | } 71 | 72 | public void setCanRestart(boolean canRestart) { 73 | isCanRestart = canRestart; 74 | } 75 | 76 | 77 | public ITree(INode root, T context) { 78 | this.root = root; 79 | this.context = context; 80 | //将上下文传递进去 81 | enableBahacvior(); 82 | } 83 | 84 | //启动行为树 85 | private void enableBahacvior() { 86 | //初始化运行栈 87 | this.activeStack.push(new Stack()); 88 | this.parentIndex.add(-1); 89 | this.relativeChildIndex.add(-1); 90 | this.parentCompositeIndex.add(-1); 91 | this.addToNodeList(this.root, -1); 92 | this.pushNode(0, 0); 93 | } 94 | 95 | 96 | private void addToNodeList(INode node, int parentCompositeIndex) { 97 | //保存当前节点 98 | this.nodeList.add(node); 99 | node.setContext(this.context); 100 | //当前元素在nodeLits的索引 101 | int index = this.nodeList.size() - 1; 102 | //拥有子元素 103 | if (node instanceof IParentNode) { 104 | IParentNode parentNode = (IParentNode) node; 105 | 106 | //如果是父节点类型,给孩子索引增加空数组 107 | this.childrenIndex.add(new ArrayList()); 108 | this.childConditionalIndex.add(new ArrayList()); 109 | for (int i = 0; i < parentNode.children.size(); i++) { 110 | //前序遍历 ... 111 | //给每个孩子保存父节点内容 112 | this.parentIndex.add(index); 113 | //保存当前父节点的孩子索引 114 | this.childrenIndex.get(index).add(this.nodeList.size()); 115 | //保存当前节点的是父亲第几个孩子索引 116 | this.relativeChildIndex.add(i); 117 | //防止装入装饰节点 118 | if (node instanceof ICompositeNode) { 119 | parentCompositeIndex = index; 120 | } 121 | //该节点的上层组合节点索引 122 | this.parentCompositeIndex.add(parentCompositeIndex); 123 | //前序遍历 ... 124 | this.addToNodeList(parentNode.children.get(i), parentCompositeIndex); 125 | } 126 | } else { 127 | //没有子元素的节点 128 | this.childrenIndex.add(null); 129 | this.childConditionalIndex.add(null); 130 | if (node instanceof IConditionalNode) { 131 | //如果是条件节点 找出上层的组合节点 132 | parentCompositeIndex = this.parentCompositeIndex.get(index); 133 | if (parentCompositeIndex != -1) { 134 | //给上层组合节点增加孩子是条件节点的索引 135 | this.childConditionalIndex.get(parentCompositeIndex).add(index); 136 | } 137 | } 138 | } 139 | } 140 | 141 | private void pushNode(int index, int stackIndex) { 142 | Stack stack = this.activeStack.get(stackIndex); 143 | if (stack.size() == 0 || stack.get(stack.size() - 1) != index) { 144 | stack.push(index); 145 | INode node = this.nodeList.get(index); 146 | // if (isOpenLog) { 147 | // log.info("pushNode {}", node); 148 | // } 149 | //执行初始化方法 150 | node.onStart(); 151 | } 152 | } 153 | 154 | //运行行为树 155 | public void tick() { 156 | //条件判断 并回滚 157 | // this.reevaluateConftionalNode() 158 | //遍历所有的运行栈 159 | for (int i = this.activeStack.size() - 1; i >= 0; i--) { 160 | //拿到运行栈 161 | Stack stack = this.activeStack.get(i); 162 | //上一次执行的节点索引 163 | int preIndex = -1; 164 | //上一次执行的节点状态 165 | NodeStatus preStatus = NodeStatus.Inactive; 166 | //当运行栈中有节点时执行 167 | while (preStatus != NodeStatus.Running && i < this.activeStack.size() && !stack.isEmpty()) { 168 | //找出当前运行节点的索引 169 | int curIndex = stack.get(stack.size() - 1); 170 | //如果本次和上次一样则跳过(防止重复执行) 171 | if (preIndex == curIndex) { 172 | break; 173 | } 174 | //更新前置节点 175 | preIndex = curIndex; 176 | //执行节点,并更新前置节点状态 177 | preStatus = this.runNode(curIndex, i, preStatus); 178 | } 179 | 180 | } 181 | 182 | } 183 | 184 | public NodeStatus runNode(int index, int stackIndex, NodeStatus preStatus) { 185 | //将节点推入运行栈,并执行节点的onStart方法(内部有判断,如果当前节点已经在运行栈中就不执行) 186 | this.pushNode(index, stackIndex); 187 | //拿到当前节点 188 | INode node = this.nodeList.get(index); 189 | NodeStatus status = preStatus; 190 | if (node instanceof IParentNode) { 191 | IParentNode parentNode = (IParentNode) node; 192 | //如果是父节点,执行父节点逻辑 193 | status = this.runParentNode(index, stackIndex, preStatus); 194 | if (parentNode.canRunParallelChildren()) { 195 | status = node.getStatus(); 196 | } 197 | } else { 198 | //普通节点执行节点的onUpdate方法 199 | status = node.onUpdate(this.context); 200 | } 201 | //如果节点已经执行完毕,非running状态,则推出运行栈,并执行onEnd方法,和父节点的onChildExecuted方法,改变运行孩子索引和父节点的状态 202 | if (status != NodeStatus.Running) { 203 | status = this.popNode(index, stackIndex, status, true); 204 | } 205 | //将本次节点运行的状态返回 206 | return status; 207 | } 208 | 209 | public NodeStatus runParentNode(int index, int stackIndex, NodeStatus preStatus) { 210 | //强转为父节点 211 | IParentNode parentNode = (IParentNode) this.nodeList.get(index); 212 | if (!parentNode.canRunParallelChildren() || parentNode.getStatus() != NodeStatus.Running) { 213 | NodeStatus childStatus = NodeStatus.Inactive; 214 | //如果父节点可执行,并且状态非running 则继续执行 215 | while (parentNode.canExecute() && (childStatus != NodeStatus.Running || parentNode.canRunParallelChildren())) { 216 | int childIndex = parentNode.getIndex(); 217 | //如果是并行节点,增加运行栈将任务都推送进去 218 | if (parentNode.canRunParallelChildren()) { 219 | this.activeStack.push(new Stack()); 220 | stackIndex = this.activeStack.size() - 1; 221 | //增加parentNode index 222 | parentNode.onChildStarted(); 223 | } 224 | //执行孩子节点 225 | childStatus = preStatus = this.runNode(this.childrenIndex.get(index).get(childIndex), stackIndex, preStatus); 226 | } 227 | } 228 | 229 | return preStatus; 230 | } 231 | 232 | public NodeStatus popNode(int index, int stackIndex, NodeStatus status, boolean popChildren) { 233 | Stack stack = this.activeStack.get(stackIndex); 234 | //当前运行节点出栈 235 | stack.pop(); 236 | //拿到当前节点 237 | INode node = this.nodeList.get(index); 238 | //执行onEnd方法 239 | node.onEnd(); 240 | // if (isOpenLog) { 241 | // log.info("popNode {}", node); 242 | // } 243 | //拿到当前节点的父节点索引 244 | int parentIndex = this.parentIndex.get(index); 245 | //如果有父节点 246 | if (parentIndex != -1) { 247 | //当前节点是条件节点,生成条件重新评估对象 248 | if (node instanceof IConditionalNode) { 249 | //拿到当前节点的父组合节点 250 | int parentCompositeIndex = this.parentCompositeIndex.get(index); 251 | //如果有 252 | if (parentCompositeIndex != -1) { 253 | //拿到父组合节点 254 | ICompositeNode compositeNode = (ICompositeNode) this.nodeList.get(parentCompositeIndex); 255 | //如果父组合节点有重新评估类型 则生成 256 | if (compositeNode.getAbortType() != AbortType.None) { 257 | if (this.conditionalReevaluateMap.containsKey(index)) { 258 | //如果有先不要执行 259 | ConditionalReevaluate conditionalReevaluate = this.conditionalReevaluateMap.get(index); 260 | 261 | conditionalReevaluate.setCompositeIndex(-1); 262 | conditionalReevaluate.setStatus(status); 263 | } else { 264 | //如果上级组合节点是低优先级,则本次不执行,等到执行到行为树右边时再执行 265 | ConditionalReevaluate conditionalReevaluate = new ConditionalReevaluate(index, status, compositeNode.getAbortType() == AbortType.LowPriority ? -1 : parentCompositeIndex); 266 | this.conditionalReevaluateList.add(conditionalReevaluate); 267 | this.conditionalReevaluateMap.put(index, conditionalReevaluate); 268 | } 269 | } 270 | 271 | } 272 | } 273 | 274 | IParentNode parentNode = (IParentNode) this.nodeList.get(parentIndex); 275 | if (node instanceof IDecoratorNode) { 276 | //如果是装饰节点 则将状态装饰 277 | status = ((IDecoratorNode) node).decorator(status); 278 | } 279 | //运行子状态影响父状态的方法 280 | parentNode.onChildExecuted(status, this.relativeChildIndex.get(index)); 281 | } 282 | 283 | //如果当前节点是组合节点退出 284 | if (node instanceof ICompositeNode) { 285 | ICompositeNode compositeNode = (ICompositeNode) node; 286 | //如果条件重新评估 只影响自己 或者 没有,或者当前运行栈已经没有运行节点了,则清空以当前节点为父节点的的条件重评估 287 | if (compositeNode.getAbortType() == AbortType.Self || compositeNode.getAbortType() == AbortType.None || stack.isEmpty()) { 288 | this.removeChildConditionalReevalute(index); 289 | //如果当前节点的重评估状态为低优先级,或者Both,则要将当前节点为父节点的条件重评估,父节点向上移动 290 | } else if (compositeNode.getAbortType() == AbortType.LowPriority || compositeNode.getAbortType() == AbortType.Both) { 291 | //遍历该节点下所有的条件节点(必须遍历 有可能当前节点下的条件重新评估指向-1,先不执行,当父节点退出时,指向上级,开始执行) 292 | for (int i = 0; i < this.childConditionalIndex.get(index).size(); i++) { 293 | //拿到条件节点的索引 294 | int curNodeIndex = this.childConditionalIndex.get(index).get(i); 295 | //如果有条件重评估 296 | if (this.conditionalReevaluateMap.containsKey(curNodeIndex)) { 297 | //拿到条件重评估 298 | ConditionalReevaluate conditionalReevaluate = this.conditionalReevaluateMap.get(curNodeIndex); 299 | //将父节点指向当前节点的父组合节点 300 | conditionalReevaluate.setCompositeIndex(this.parentCompositeIndex.get(index)); 301 | } 302 | } 303 | //遍历所有的条件重评估对象 把有可能是下级传递上来的条件重评估再往上传递 304 | for (ConditionalReevaluate conditionalReevaluate : this.conditionalReevaluateList) { 305 | if (conditionalReevaluate.getCompositeIndex() == index) { 306 | conditionalReevaluate.setCompositeIndex(this.parentCompositeIndex.get(index)); 307 | } 308 | } 309 | } 310 | } 311 | //并行节点返回失败将其他节点pop出去 312 | if (popChildren) { 313 | //拿到右边的运行栈 314 | for (int i = this.activeStack.size() - 1; i < stackIndex; i--) { 315 | Stack curStack = this.activeStack.get(i); 316 | if (!curStack.isEmpty() && this.isParentNode(index, curStack.get(curStack.size() - 1))) { 317 | for (int j = curStack.size() - 1; j >= 0; j--) { 318 | this.popNode(curStack.get(curStack.size() - 1), i, NodeStatus.Failure, false); 319 | 320 | } 321 | } 322 | 323 | } 324 | } 325 | 326 | 327 | //如果当前没有可以运行的节点了则重新开始行为树 328 | if (stack.isEmpty()) { 329 | if (stackIndex == 0) { 330 | //所有运行栈都运行完成了 331 | this.restart(); 332 | } else { 333 | //当前栈运行完毕,其他栈还有 删除当前栈 334 | this.activeStack.remove(stackIndex); 335 | } 336 | } 337 | //返回状态 338 | return status; 339 | } 340 | 341 | private void removeChildConditionalReevalute(int index) { 342 | for (int i = this.conditionalReevaluateList.size() - 1; i >= 0; i--) { 343 | ConditionalReevaluate cur = this.conditionalReevaluateList.get(i); 344 | if (cur.getCompositeIndex() == index) { 345 | this.conditionalReevaluateMap.remove(cur.getIndex()); 346 | this.conditionalReevaluateList.remove(i); 347 | } 348 | 349 | } 350 | } 351 | 352 | private boolean isParentNode(int parentIndex, int childIndex) { 353 | for (int i = childIndex; i != -1; i = this.parentIndex.get(i)) { 354 | if (i == parentIndex) { 355 | return true; 356 | } 357 | } 358 | return false; 359 | 360 | } 361 | 362 | private void restart() { 363 | if (this.isCanRestart) { 364 | this.removeChildConditionalReevalute(-1); 365 | this.pushNode(0, 0); 366 | } 367 | } 368 | 369 | private void reevaluateConftionalNode() { 370 | //倒序遍历收集到的重评估对象 371 | for (int i = this.conditionalReevaluateList.size() - 1; i >= 0; i--) { 372 | final ConditionalReevaluate conditionalReevaluate = this.conditionalReevaluateList.get(i); 373 | int index = conditionalReevaluate.getIndex(); 374 | NodeStatus preStatus = conditionalReevaluate.getStatus(); 375 | int compositeIndex = conditionalReevaluate.getCompositeIndex(); 376 | //组合节点是-1的时候跳过 377 | if (compositeIndex == -1) { 378 | continue; 379 | } 380 | NodeStatus status = this.nodeList.get(index).onUpdate(this.context); 381 | //状态没有变化时跳过 382 | if (status == preStatus) { 383 | continue; 384 | } 385 | 386 | for (int j = this.activeStack.size() - 1; j >= 0; j--) { 387 | Stack stack = this.activeStack.get(j); 388 | //首先找到当前节点和条件变化的节点的共同父节点 389 | int curNodeIndex = stack.get(stack.size() - 1); 390 | int commonParentIndex = this.findCommonParentIndex(curNodeIndex, index); 391 | if (this.isParentNode(compositeIndex, commonParentIndex)) { 392 | int stackLen = this.activeStack.size(); 393 | //1、把当前节点的所有父节点退出运行栈 394 | while (curNodeIndex != -1 && curNodeIndex != commonParentIndex && stackLen == this.activeStack.size()) { 395 | this.popNode(curNodeIndex, j, NodeStatus.Failure, false); 396 | curNodeIndex = this.parentIndex.get(curNodeIndex); 397 | } 398 | } 399 | 400 | } 401 | 402 | //2、把公共节点下最顶级的父节点的右侧的条件重评估移除 403 | //倒序遍历 j -》 i的所有重评估对象都要删掉 404 | for (int j = this.conditionalReevaluateList.size() - 1; j >= i; j--) { 405 | ConditionalReevaluate curCR = this.conditionalReevaluateList.get(j); 406 | //只有当前重评估节点的父组合节点是重评估的父节点才可以删除 407 | if (this.isParentNode(compositeIndex, curCR.getIndex())) { 408 | this.conditionalReevaluateMap.remove(curCR.getIndex()); 409 | this.conditionalReevaluateList.remove(j); 410 | } 411 | } 412 | //3、当前生效的条件重评估对象同一组合下的条件重评估对象停止并删除 413 | //当前重评估对象的父组合节点 414 | ICompositeNode compositeNode = (ICompositeNode) this.nodeList.get(this.parentCompositeIndex.get(index)); 415 | //遍历左边的元素 416 | for (int j = i - 1; j >= 0; j--) { 417 | ConditionalReevaluate curCR = this.conditionalReevaluateList.get(j); 418 | //如果有相同的父组合节点 419 | if (this.parentCompositeIndex.get(curCR.getIndex()).equals(this.parentCompositeIndex.get(index))) { 420 | //如果父组合重评估类型是低优先级 421 | if (compositeNode.getAbortType() == AbortType.LowPriority) { 422 | //不执行 423 | curCR.setCompositeIndex(-1); 424 | } 425 | } 426 | } 427 | //4、当前重评估的父节点到公共的父节点要重置内部的执行索引 428 | List conditionalParentIndex = new ArrayList(); 429 | for (int j = this.parentIndex.get(index); j != compositeIndex; j = this.parentIndex.get(j)) { 430 | conditionalParentIndex.add(j); 431 | } 432 | conditionalParentIndex.add(compositeIndex); 433 | for (int j = conditionalParentIndex.size() - 1; j >= 0; j--) { 434 | IParentNode parentNode = (IParentNode) this.nodeList.get(conditionalParentIndex.get(j)); 435 | if (j == 0) { 436 | parentNode.onConditionalAbort(this.relativeChildIndex.get(index)); 437 | } else { 438 | parentNode.onConditionalAbort(this.relativeChildIndex.get(conditionalParentIndex.get(j-1))); 439 | } 440 | } 441 | } 442 | } 443 | 444 | private int findCommonParentIndex(int index1, int index2) { 445 | //收集index1 所有的父节点 446 | Set set = new HashSet(); 447 | int num = index1; 448 | while (num != -1) { 449 | set.add(num); 450 | num = this.parentIndex.get(num); 451 | } 452 | 453 | num = index2; 454 | while (!set.contains(num)) { 455 | num = this.parentIndex.get(num); 456 | } 457 | 458 | return num; 459 | } 460 | 461 | protected T getContext(){ 462 | return this.context; 463 | } 464 | 465 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/composite/CompositeParallel.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.composite; 2 | 3 | import cn.murfree.contents.AbortType; 4 | import cn.murfree.contents.NodeStatus; 5 | import cn.murfree.node.base.INode; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | /** 11 | * 并行节点 12 | * 所有子节点按同时执行,当有一个返回失败,则返回失败,全部成功则成功 13 | * @author lujun 14 | * @date 2022/7/22 15 | */ 16 | public class CompositeParallel extends ICompositeNode { 17 | 18 | private NodeStatus[] exectionStatus = new NodeStatus[]{}; 19 | 20 | @Override 21 | public NodeStatus getStatus() { 22 | boolean childrenComplete = true; 23 | for (NodeStatus curStatus : this.exectionStatus) { 24 | if (curStatus == NodeStatus.Running) { 25 | childrenComplete = false; 26 | } else if (curStatus == NodeStatus.Failure) { 27 | return NodeStatus.Failure; 28 | } 29 | } 30 | return childrenComplete? NodeStatus.Success: NodeStatus.Running; 31 | } 32 | 33 | @Override 34 | public void setStatus(NodeStatus status) { 35 | 36 | } 37 | 38 | 39 | public CompositeParallel() { 40 | super(); 41 | } 42 | 43 | public CompositeParallel(List children) { 44 | super(children); 45 | } 46 | 47 | public CompositeParallel(List children, AbortType abortType) { 48 | super(children, abortType); 49 | } 50 | 51 | @Override 52 | public boolean canExecute() { 53 | return this.getIndex() < this.children.size(); 54 | } 55 | 56 | @Override 57 | public void onChildExecuted(NodeStatus childStatus, int index) { 58 | this.exectionStatus[index] = childStatus; 59 | } 60 | 61 | @Override 62 | public void onStart(){ 63 | super.onStart(); 64 | this.exectionStatus = new NodeStatus[this.children.size()]; 65 | this.setIndex(0); 66 | for (int i = 0; i < this.children.size(); i++) { 67 | this.exectionStatus[i] = NodeStatus.Inactive; 68 | } 69 | } 70 | 71 | /** 72 | * 节点中断时重置状态 73 | */ 74 | @Override 75 | public void onConditionalAbort(int childIndex){ 76 | this.setIndex(0); 77 | Arrays.fill(this.exectionStatus, NodeStatus.Inactive); 78 | } 79 | 80 | 81 | @Override 82 | public boolean canRunParallelChildren(){ 83 | return true; 84 | } 85 | 86 | @Override 87 | public void onChildStarted(){ 88 | this.exectionStatus[this.getIndex()] = NodeStatus.Running; 89 | this.setIndex(this.getIndex()+1); 90 | } 91 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/composite/CompositeRandomSelevtor.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.composite; 2 | 3 | 4 | import cn.murfree.contents.AbortType; 5 | import cn.murfree.contents.NodeStatus; 6 | import cn.murfree.node.base.INode; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Stack; 11 | 12 | import static cn.murfree.contents.NodeStatus.*; 13 | 14 | 15 | /** 16 | * 选择随机节点,所有子节点随机执行,当有一个返回成功则中止返回成功,全部节点返回失败,则该节点返回失败 17 | * 18 | * @author lujun 19 | * @date 2022/7/22 20 | */ 21 | public class CompositeRandomSelevtor extends ICompositeNode { 22 | 23 | //真正执行的顺序 24 | private final Stack executionOrder = new Stack(); 25 | 26 | public CompositeRandomSelevtor() { 27 | super(); 28 | } 29 | public CompositeRandomSelevtor(List children) { 30 | super(children); 31 | } 32 | 33 | public CompositeRandomSelevtor(List children, AbortType abortType) { 34 | super(children, abortType); 35 | } 36 | 37 | @Override 38 | public boolean canExecute() { 39 | return this.executionOrder.size() > 0 && this.getStatus() != Success; 40 | } 41 | 42 | @Override 43 | public void onChildExecuted(NodeStatus childStatus, int index) { 44 | switch (childStatus){ 45 | case Running: 46 | this.setStatus(Running); 47 | break; 48 | case Success: 49 | this.setStatus(Success); 50 | break; 51 | case Failure: 52 | this.executionOrder.pop(); 53 | if(this.executionOrder.isEmpty()){ 54 | this.setStatus(Failure); 55 | }else { 56 | this.setStatus(Running); 57 | } 58 | break; 59 | default: 60 | break; 61 | } 62 | } 63 | 64 | @Override 65 | public int getIndex(){ 66 | return this.executionOrder.get(this.executionOrder.size() - 1); 67 | } 68 | 69 | @Override 70 | public void onStart(){ 71 | super.onStart(); 72 | for (int i = 0; i < this.children.size(); i++) { 73 | executionOrder.add(i); 74 | } 75 | this.shuffle(); 76 | } 77 | 78 | private void shuffle() { 79 | Collections.shuffle(this.executionOrder); 80 | } 81 | 82 | 83 | @Override 84 | public void onConditionalAbort(int childIndex){ 85 | this.shuffle(); 86 | this.setStatus(Inactive); 87 | } 88 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/composite/CompositeRandomSequence.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.composite; 2 | 3 | 4 | import cn.murfree.contents.AbortType; 5 | import cn.murfree.contents.NodeStatus; 6 | import cn.murfree.node.base.INode; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Stack; 11 | 12 | import static cn.murfree.contents.NodeStatus.*; 13 | 14 | /** 15 | * 顺序随机节点,所有子节点随机执行,当有一个返回失败,则中止并返回失败 16 | * 17 | * @author lujun 18 | * @date 2022/7/22 19 | */ 20 | public class CompositeRandomSequence extends ICompositeNode { 21 | //真正执行的顺序 22 | private final Stack executionOrder = new Stack(); 23 | 24 | 25 | public CompositeRandomSequence() { 26 | super(); 27 | } 28 | 29 | public CompositeRandomSequence(List children) { 30 | super(children); 31 | } 32 | 33 | public CompositeRandomSequence(List children, AbortType abortType) { 34 | super(children, abortType); 35 | } 36 | 37 | @Override 38 | public boolean canExecute() { 39 | return this.executionOrder.size() > 0 && this.getStatus() != Failure; 40 | } 41 | 42 | @Override 43 | public void onChildExecuted(NodeStatus childStatus, int index) { 44 | switch (childStatus){ 45 | case Running: 46 | this.setStatus(Running); 47 | break; 48 | case Success: 49 | this.executionOrder.pop(); 50 | if(this.executionOrder.isEmpty()){ 51 | this.setStatus(Success); 52 | }else { 53 | this.setStatus(Running); 54 | } 55 | break; 56 | case Failure: 57 | this.setStatus(Success); 58 | break; 59 | default: 60 | break; 61 | } 62 | } 63 | 64 | @Override 65 | public int getIndex(){ 66 | return this.executionOrder.get(this.executionOrder.size() - 1); 67 | } 68 | 69 | @Override 70 | public void onStart(){ 71 | super.onStart(); 72 | for (int i = 0; i < this.children.size(); i++) { 73 | executionOrder.add(i); 74 | } 75 | this.shuffle(); 76 | } 77 | 78 | private void shuffle() { 79 | Collections.shuffle(this.executionOrder); 80 | } 81 | 82 | @Override 83 | public void onConditionalAbort(int childIndex){ 84 | this.shuffle(); 85 | this.setStatus(Inactive); 86 | } 87 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/composite/CompositeSelector.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.composite; 2 | 3 | 4 | import cn.murfree.contents.AbortType; 5 | import cn.murfree.contents.NodeStatus; 6 | import cn.murfree.node.base.INode; 7 | 8 | import java.util.List; 9 | import static cn.murfree.contents.NodeStatus.*; 10 | /** 11 | * 选择节点,所有子节点按顺序执行,当有一个返回成功则中止返回成功,全部节点返回失败,则该节点返回失败 12 | * 13 | * @author lujun 14 | * @date 2022/7/22 15 | */ 16 | public class CompositeSelector extends ICompositeNode { 17 | 18 | public CompositeSelector() { 19 | super(); 20 | } 21 | 22 | public CompositeSelector(List children) { 23 | super(children); 24 | } 25 | 26 | public CompositeSelector(List children, AbortType abortType) { 27 | super(children, abortType); 28 | } 29 | 30 | 31 | @Override 32 | public void onStart(){ 33 | super.onStart(); 34 | this.setIndex(0); 35 | } 36 | 37 | @Override 38 | public void onConditionalAbort(int childIndex) { 39 | this.setIndex(childIndex); 40 | this.setStatus(Inactive); 41 | } 42 | 43 | @Override 44 | public boolean canExecute() { 45 | return this.getIndex() < this.children.size() && this.getStatus() != Success; 46 | } 47 | 48 | @Override 49 | public void onChildExecuted(NodeStatus childStatus, int index) { 50 | switch (childStatus){ 51 | case Inactive: 52 | break; 53 | case Running: 54 | this.setStatus((Running)); 55 | break; 56 | case Success: 57 | this.setStatus((Success)); 58 | break; 59 | case Failure: 60 | this.setIndex(this.getIndex()+1); 61 | if(this.getIndex() >= this.children.size()){ 62 | this.setStatus((Failure)); 63 | }else { 64 | this.setStatus((Running)); 65 | } 66 | break; 67 | default: 68 | break; 69 | } 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/composite/CompositeSequence.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.composite; 2 | 3 | import cn.murfree.contents.AbortType; 4 | import cn.murfree.contents.NodeStatus; 5 | import cn.murfree.node.base.INode; 6 | 7 | import java.util.List; 8 | 9 | import static cn.murfree.contents.NodeStatus.*; 10 | /** 11 | * 顺序节点,所有子节点按顺序执行,当有一个返回失败,则中止并返回失败 12 | * 13 | * @author lujun 14 | * @date 2022/7/22 15 | */ 16 | public class CompositeSequence extends ICompositeNode { 17 | 18 | public CompositeSequence() { 19 | super(); 20 | } 21 | 22 | public CompositeSequence(List children) { 23 | super(children); 24 | } 25 | 26 | public CompositeSequence(List children, AbortType abortType) { 27 | super(children, abortType); 28 | } 29 | 30 | 31 | @Override 32 | public void onStart(){ 33 | super.onStart(); 34 | this.setIndex(0); 35 | } 36 | 37 | @Override 38 | public void onConditionalAbort(int childIndex) { 39 | this.setIndex(childIndex); 40 | this.setStatus(Inactive); 41 | } 42 | 43 | @Override 44 | public boolean canExecute() { 45 | return this.getIndex() < this.children.size() && this.getStatus() != Failure; 46 | } 47 | 48 | @Override 49 | public void onChildExecuted(NodeStatus childStatus, int index) { 50 | switch (childStatus){ 51 | case Inactive: 52 | break; 53 | case Running: 54 | this.setStatus((Running)); 55 | break; 56 | case Success: 57 | this.setIndex(this.getIndex()+1); 58 | if(this.getIndex() >= this.children.size()){ 59 | this.setStatus((Success)); 60 | }else { 61 | this.setStatus((Running)); 62 | } 63 | break; 64 | case Failure: 65 | this.setStatus((Failure)); 66 | break; 67 | default: 68 | break; 69 | } 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/composite/ICompositeNode.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.composite; 2 | 3 | import cn.murfree.contents.AbortType; 4 | import cn.murfree.node.base.INode; 5 | import cn.murfree.node.base.IParentNode; 6 | 7 | import java.util.List; 8 | 9 | 10 | /** 11 | * 基础组合节点 12 | * 13 | * @author lujun 14 | * @date 2022/7/22 15 | */ 16 | public abstract class ICompositeNode extends IParentNode { 17 | /** 18 | * 中断类型 19 | */ 20 | private AbortType abortType; 21 | 22 | public AbortType getAbortType() { 23 | return abortType; 24 | } 25 | 26 | public void setAbortType(AbortType abortType) { 27 | this.abortType = abortType; 28 | } 29 | 30 | public ICompositeNode() { 31 | super(); 32 | this.abortType = AbortType.None; 33 | } 34 | public ICompositeNode(List children) { 35 | super(children); 36 | this.abortType = AbortType.None; 37 | } 38 | 39 | public ICompositeNode(List children, AbortType abortType) { 40 | super(children); 41 | this.abortType = abortType == null ? AbortType.None : abortType; 42 | } 43 | 44 | /** 45 | * 节点中断时重置状态 46 | */ 47 | @Override 48 | public abstract void onConditionalAbort(int childIndex); 49 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/decorator/DecoratorInverter.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.decorator; 2 | 3 | 4 | import cn.murfree.contents.NodeStatus; 5 | import cn.murfree.node.base.INode; 6 | 7 | import java.util.List; 8 | import static cn.murfree.contents.NodeStatus.*; 9 | 10 | /** 11 | * 反转装饰节点 12 | * 13 | * @author lujun 14 | * @date 2022/7/22 15 | */ 16 | public class DecoratorInverter extends IDecoratorNode { 17 | 18 | public DecoratorInverter() { 19 | super(); 20 | } 21 | 22 | public DecoratorInverter(List children) { 23 | super(children); 24 | } 25 | 26 | @Override 27 | public boolean canExecute() { 28 | return this.getStatus() == NodeStatus.Inactive || this.getStatus() == NodeStatus.Running; 29 | } 30 | 31 | @Override 32 | public void onChildExecuted(NodeStatus childStatus, int index) { 33 | this.setStatus(childStatus); 34 | } 35 | 36 | @Override 37 | public NodeStatus decorator(NodeStatus status) { 38 | switch (status) { 39 | case Failure: 40 | return Success; 41 | case Success: 42 | return Failure; 43 | default: 44 | return status; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/node/decorator/IDecoratorNode.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.node.decorator; 2 | 3 | import cn.murfree.contents.NodeStatus; 4 | import cn.murfree.node.base.INode; 5 | import cn.murfree.node.base.IParentNode; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 基础包装节点 11 | * 12 | * @author lujun 13 | * @date 2022/7/22 14 | */ 15 | public abstract class IDecoratorNode extends IParentNode { 16 | 17 | 18 | public IDecoratorNode() { 19 | super(); 20 | } 21 | 22 | public IDecoratorNode(List children) { 23 | super(children); 24 | } 25 | 26 | public NodeStatus decorator(NodeStatus status){ 27 | return this.getStatus(); 28 | } 29 | 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/cn/murfree/utils/MurFreeBuilder.java: -------------------------------------------------------------------------------- 1 | package cn.murfree.utils; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import cn.murfree.node.base.INode; 5 | import cn.murfree.node.base.IParentNode; 6 | 7 | /** 8 | * TreeBuild 9 | * 10 | * @author lujun 11 | * @date 2022/7/27 12 | */ 13 | public class MurFreeBuilder { 14 | 15 | 16 | 17 | public static INode build(JsonNode jsonNode) throws Exception { 18 | final String nodeName = jsonNode.get("name").textValue(); 19 | final String className = jsonNode.get("content").textValue(); 20 | //创建该类 21 | final INode node = (INode) Class.forName(className).newInstance(); 22 | node.setName(nodeName); 23 | if(jsonNode.has("children")){ 24 | final IParentNode parentNode = (IParentNode) node; 25 | final JsonNode children = jsonNode.get("children"); 26 | for (JsonNode child : children) { 27 | parentNode.addChild(build(child)); 28 | } 29 | } 30 | return node; 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/font-awesome.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | /* FONT PATH 6 | * -------------------------- */ 7 | @font-face { 8 | font-family: 'FontAwesome'; 9 | src: url('../fonts/fontawesome-webfont.eot?v=4.4.0'); 10 | src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg'); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | .fa { 15 | display: inline-block; 16 | font: normal normal normal 14px/1 FontAwesome; 17 | font-size: inherit; 18 | text-rendering: auto; 19 | -webkit-font-smoothing: antialiased; 20 | -moz-osx-font-smoothing: grayscale; 21 | } 22 | /* makes the font 33% larger relative to the icon container */ 23 | .fa-lg { 24 | font-size: 1.33333333em; 25 | line-height: 0.75em; 26 | vertical-align: -15%; 27 | } 28 | .fa-2x { 29 | font-size: 2em; 30 | } 31 | .fa-3x { 32 | font-size: 3em; 33 | } 34 | .fa-4x { 35 | font-size: 4em; 36 | } 37 | .fa-5x { 38 | font-size: 5em; 39 | } 40 | .fa-fw { 41 | width: 1.28571429em; 42 | text-align: center; 43 | } 44 | .fa-ul { 45 | padding-left: 0; 46 | margin-left: 2.14285714em; 47 | list-style-type: none; 48 | } 49 | .fa-ul > li { 50 | position: relative; 51 | } 52 | .fa-li { 53 | position: absolute; 54 | left: -2.14285714em; 55 | width: 2.14285714em; 56 | top: 0.14285714em; 57 | text-align: center; 58 | } 59 | .fa-li.fa-lg { 60 | left: -1.85714286em; 61 | } 62 | .fa-border { 63 | padding: .2em .25em .15em; 64 | border: solid 0.08em #eeeeee; 65 | border-radius: .1em; 66 | } 67 | .fa-pull-left { 68 | float: left; 69 | } 70 | .fa-pull-right { 71 | float: right; 72 | } 73 | .fa.fa-pull-left { 74 | margin-right: .3em; 75 | } 76 | .fa.fa-pull-right { 77 | margin-left: .3em; 78 | } 79 | /* Deprecated as of 4.4.0 */ 80 | .pull-right { 81 | float: right; 82 | } 83 | .pull-left { 84 | float: left; 85 | } 86 | .fa.pull-left { 87 | margin-right: .3em; 88 | } 89 | .fa.pull-right { 90 | margin-left: .3em; 91 | } 92 | .fa-spin { 93 | -webkit-animation: fa-spin 2s infinite linear; 94 | animation: fa-spin 2s infinite linear; 95 | } 96 | .fa-pulse { 97 | -webkit-animation: fa-spin 1s infinite steps(8); 98 | animation: fa-spin 1s infinite steps(8); 99 | } 100 | @-webkit-keyframes fa-spin { 101 | 0% { 102 | -webkit-transform: rotate(0deg); 103 | transform: rotate(0deg); 104 | } 105 | 100% { 106 | -webkit-transform: rotate(359deg); 107 | transform: rotate(359deg); 108 | } 109 | } 110 | @keyframes fa-spin { 111 | 0% { 112 | -webkit-transform: rotate(0deg); 113 | transform: rotate(0deg); 114 | } 115 | 100% { 116 | -webkit-transform: rotate(359deg); 117 | transform: rotate(359deg); 118 | } 119 | } 120 | .fa-rotate-90 { 121 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); 122 | -webkit-transform: rotate(90deg); 123 | -ms-transform: rotate(90deg); 124 | transform: rotate(90deg); 125 | } 126 | .fa-rotate-180 { 127 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); 128 | -webkit-transform: rotate(180deg); 129 | -ms-transform: rotate(180deg); 130 | transform: rotate(180deg); 131 | } 132 | .fa-rotate-270 { 133 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); 134 | -webkit-transform: rotate(270deg); 135 | -ms-transform: rotate(270deg); 136 | transform: rotate(270deg); 137 | } 138 | .fa-flip-horizontal { 139 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); 140 | -webkit-transform: scale(-1, 1); 141 | -ms-transform: scale(-1, 1); 142 | transform: scale(-1, 1); 143 | } 144 | .fa-flip-vertical { 145 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); 146 | -webkit-transform: scale(1, -1); 147 | -ms-transform: scale(1, -1); 148 | transform: scale(1, -1); 149 | } 150 | :root .fa-rotate-90, 151 | :root .fa-rotate-180, 152 | :root .fa-rotate-270, 153 | :root .fa-flip-horizontal, 154 | :root .fa-flip-vertical { 155 | filter: none; 156 | } 157 | .fa-stack { 158 | position: relative; 159 | display: inline-block; 160 | width: 2em; 161 | height: 2em; 162 | line-height: 2em; 163 | vertical-align: middle; 164 | } 165 | .fa-stack-1x, 166 | .fa-stack-2x { 167 | position: absolute; 168 | left: 0; 169 | width: 100%; 170 | text-align: center; 171 | } 172 | .fa-stack-1x { 173 | line-height: inherit; 174 | } 175 | .fa-stack-2x { 176 | font-size: 2em; 177 | } 178 | .fa-inverse { 179 | color: #ffffff; 180 | } 181 | /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen 182 | readers do not read off random characters that represent icons */ 183 | .fa-glass:before { 184 | content: "\f000"; 185 | } 186 | .fa-music:before { 187 | content: "\f001"; 188 | } 189 | .fa-search:before { 190 | content: "\f002"; 191 | } 192 | .fa-envelope-o:before { 193 | content: "\f003"; 194 | } 195 | .fa-heart:before { 196 | content: "\f004"; 197 | } 198 | .fa-star:before { 199 | content: "\f005"; 200 | } 201 | .fa-star-o:before { 202 | content: "\f006"; 203 | } 204 | .fa-user:before { 205 | content: "\f007"; 206 | } 207 | .fa-film:before { 208 | content: "\f008"; 209 | } 210 | .fa-th-large:before { 211 | content: "\f009"; 212 | } 213 | .fa-th:before { 214 | content: "\f00a"; 215 | } 216 | .fa-th-list:before { 217 | content: "\f00b"; 218 | } 219 | .fa-check:before { 220 | content: "\f00c"; 221 | } 222 | .fa-remove:before, 223 | .fa-close:before, 224 | .fa-times:before { 225 | content: "\f00d"; 226 | } 227 | .fa-search-plus:before { 228 | content: "\f00e"; 229 | } 230 | .fa-search-minus:before { 231 | content: "\f010"; 232 | } 233 | .fa-power-off:before { 234 | content: "\f011"; 235 | } 236 | .fa-signal:before { 237 | content: "\f012"; 238 | } 239 | .fa-gear:before, 240 | .fa-cog:before { 241 | content: "\f013"; 242 | } 243 | .fa-trash-o:before { 244 | content: "\f014"; 245 | } 246 | .fa-home:before { 247 | content: "\f015"; 248 | } 249 | .fa-file-o:before { 250 | content: "\f016"; 251 | } 252 | .fa-clock-o:before { 253 | content: "\f017"; 254 | } 255 | .fa-road:before { 256 | content: "\f018"; 257 | } 258 | .fa-download:before { 259 | content: "\f019"; 260 | } 261 | .fa-arrow-circle-o-down:before { 262 | content: "\f01a"; 263 | } 264 | .fa-arrow-circle-o-up:before { 265 | content: "\f01b"; 266 | } 267 | .fa-inbox:before { 268 | content: "\f01c"; 269 | } 270 | .fa-play-circle-o:before { 271 | content: "\f01d"; 272 | } 273 | .fa-rotate-right:before, 274 | .fa-repeat:before { 275 | content: "\f01e"; 276 | } 277 | .fa-refresh:before { 278 | content: "\f021"; 279 | } 280 | .fa-list-alt:before { 281 | content: "\f022"; 282 | } 283 | .fa-lock:before { 284 | content: "\f023"; 285 | } 286 | .fa-flag:before { 287 | content: "\f024"; 288 | } 289 | .fa-headphones:before { 290 | content: "\f025"; 291 | } 292 | .fa-volume-off:before { 293 | content: "\f026"; 294 | } 295 | .fa-volume-down:before { 296 | content: "\f027"; 297 | } 298 | .fa-volume-up:before { 299 | content: "\f028"; 300 | } 301 | .fa-qrcode:before { 302 | content: "\f029"; 303 | } 304 | .fa-barcode:before { 305 | content: "\f02a"; 306 | } 307 | .fa-tag:before { 308 | content: "\f02b"; 309 | } 310 | .fa-tags:before { 311 | content: "\f02c"; 312 | } 313 | .fa-book:before { 314 | content: "\f02d"; 315 | } 316 | .fa-bookmark:before { 317 | content: "\f02e"; 318 | } 319 | .fa-print:before { 320 | content: "\f02f"; 321 | } 322 | .fa-camera:before { 323 | content: "\f030"; 324 | } 325 | .fa-font:before { 326 | content: "\f031"; 327 | } 328 | .fa-bold:before { 329 | content: "\f032"; 330 | } 331 | .fa-italic:before { 332 | content: "\f033"; 333 | } 334 | .fa-text-height:before { 335 | content: "\f034"; 336 | } 337 | .fa-text-width:before { 338 | content: "\f035"; 339 | } 340 | .fa-align-left:before { 341 | content: "\f036"; 342 | } 343 | .fa-align-center:before { 344 | content: "\f037"; 345 | } 346 | .fa-align-right:before { 347 | content: "\f038"; 348 | } 349 | .fa-align-justify:before { 350 | content: "\f039"; 351 | } 352 | .fa-list:before { 353 | content: "\f03a"; 354 | } 355 | .fa-dedent:before, 356 | .fa-outdent:before { 357 | content: "\f03b"; 358 | } 359 | .fa-indent:before { 360 | content: "\f03c"; 361 | } 362 | .fa-video-camera:before { 363 | content: "\f03d"; 364 | } 365 | .fa-photo:before, 366 | .fa-image:before, 367 | .fa-picture-o:before { 368 | content: "\f03e"; 369 | } 370 | .fa-pencil:before { 371 | content: "\f040"; 372 | } 373 | .fa-map-marker:before { 374 | content: "\f041"; 375 | } 376 | .fa-adjust:before { 377 | content: "\f042"; 378 | } 379 | .fa-tint:before { 380 | content: "\f043"; 381 | } 382 | .fa-edit:before, 383 | .fa-pencil-square-o:before { 384 | content: "\f044"; 385 | } 386 | .fa-share-square-o:before { 387 | content: "\f045"; 388 | } 389 | .fa-check-square-o:before { 390 | content: "\f046"; 391 | } 392 | .fa-arrows:before { 393 | content: "\f047"; 394 | } 395 | .fa-step-backward:before { 396 | content: "\f048"; 397 | } 398 | .fa-fast-backward:before { 399 | content: "\f049"; 400 | } 401 | .fa-backward:before { 402 | content: "\f04a"; 403 | } 404 | .fa-play:before { 405 | content: "\f04b"; 406 | } 407 | .fa-pause:before { 408 | content: "\f04c"; 409 | } 410 | .fa-stop:before { 411 | content: "\f04d"; 412 | } 413 | .fa-forward:before { 414 | content: "\f04e"; 415 | } 416 | .fa-fast-forward:before { 417 | content: "\f050"; 418 | } 419 | .fa-step-forward:before { 420 | content: "\f051"; 421 | } 422 | .fa-eject:before { 423 | content: "\f052"; 424 | } 425 | .fa-chevron-left:before { 426 | content: "\f053"; 427 | } 428 | .fa-chevron-right:before { 429 | content: "\f054"; 430 | } 431 | .fa-plus-circle:before { 432 | content: "\f055"; 433 | } 434 | .fa-minus-circle:before { 435 | content: "\f056"; 436 | } 437 | .fa-times-circle:before { 438 | content: "\f057"; 439 | } 440 | .fa-check-circle:before { 441 | content: "\f058"; 442 | } 443 | .fa-question-circle:before { 444 | content: "\f059"; 445 | } 446 | .fa-info-circle:before { 447 | content: "\f05a"; 448 | } 449 | .fa-crosshairs:before { 450 | content: "\f05b"; 451 | } 452 | .fa-times-circle-o:before { 453 | content: "\f05c"; 454 | } 455 | .fa-check-circle-o:before { 456 | content: "\f05d"; 457 | } 458 | .fa-ban:before { 459 | content: "\f05e"; 460 | } 461 | .fa-arrow-left:before { 462 | content: "\f060"; 463 | } 464 | .fa-arrow-right:before { 465 | content: "\f061"; 466 | } 467 | .fa-arrow-up:before { 468 | content: "\f062"; 469 | } 470 | .fa-arrow-down:before { 471 | content: "\f063"; 472 | } 473 | .fa-mail-forward:before, 474 | .fa-share:before { 475 | content: "\f064"; 476 | } 477 | .fa-expand:before { 478 | content: "\f065"; 479 | } 480 | .fa-compress:before { 481 | content: "\f066"; 482 | } 483 | .fa-plus:before { 484 | content: "\f067"; 485 | } 486 | .fa-minus:before { 487 | content: "\f068"; 488 | } 489 | .fa-asterisk:before { 490 | content: "\f069"; 491 | } 492 | .fa-exclamation-circle:before { 493 | content: "\f06a"; 494 | } 495 | .fa-gift:before { 496 | content: "\f06b"; 497 | } 498 | .fa-leaf:before { 499 | content: "\f06c"; 500 | } 501 | .fa-fire:before { 502 | content: "\f06d"; 503 | } 504 | .fa-eye:before { 505 | content: "\f06e"; 506 | } 507 | .fa-eye-slash:before { 508 | content: "\f070"; 509 | } 510 | .fa-warning:before, 511 | .fa-exclamation-triangle:before { 512 | content: "\f071"; 513 | } 514 | .fa-plane:before { 515 | content: "\f072"; 516 | } 517 | .fa-calendar:before { 518 | content: "\f073"; 519 | } 520 | .fa-random:before { 521 | content: "\f074"; 522 | } 523 | .fa-comment:before { 524 | content: "\f075"; 525 | } 526 | .fa-magnet:before { 527 | content: "\f076"; 528 | } 529 | .fa-chevron-up:before { 530 | content: "\f077"; 531 | } 532 | .fa-chevron-down:before { 533 | content: "\f078"; 534 | } 535 | .fa-retweet:before { 536 | content: "\f079"; 537 | } 538 | .fa-shopping-cart:before { 539 | content: "\f07a"; 540 | } 541 | .fa-folder:before { 542 | content: "\f07b"; 543 | } 544 | .fa-folder-open:before { 545 | content: "\f07c"; 546 | } 547 | .fa-arrows-v:before { 548 | content: "\f07d"; 549 | } 550 | .fa-arrows-h:before { 551 | content: "\f07e"; 552 | } 553 | .fa-bar-chart-o:before, 554 | .fa-bar-chart:before { 555 | content: "\f080"; 556 | } 557 | .fa-twitter-square:before { 558 | content: "\f081"; 559 | } 560 | .fa-facebook-square:before { 561 | content: "\f082"; 562 | } 563 | .fa-camera-retro:before { 564 | content: "\f083"; 565 | } 566 | .fa-key:before { 567 | content: "\f084"; 568 | } 569 | .fa-gears:before, 570 | .fa-cogs:before { 571 | content: "\f085"; 572 | } 573 | .fa-comments:before { 574 | content: "\f086"; 575 | } 576 | .fa-thumbs-o-up:before { 577 | content: "\f087"; 578 | } 579 | .fa-thumbs-o-down:before { 580 | content: "\f088"; 581 | } 582 | .fa-star-half:before { 583 | content: "\f089"; 584 | } 585 | .fa-heart-o:before { 586 | content: "\f08a"; 587 | } 588 | .fa-sign-out:before { 589 | content: "\f08b"; 590 | } 591 | .fa-linkedin-square:before { 592 | content: "\f08c"; 593 | } 594 | .fa-thumb-tack:before { 595 | content: "\f08d"; 596 | } 597 | .fa-external-link:before { 598 | content: "\f08e"; 599 | } 600 | .fa-sign-in:before { 601 | content: "\f090"; 602 | } 603 | .fa-trophy:before { 604 | content: "\f091"; 605 | } 606 | .fa-github-square:before { 607 | content: "\f092"; 608 | } 609 | .fa-upload:before { 610 | content: "\f093"; 611 | } 612 | .fa-lemon-o:before { 613 | content: "\f094"; 614 | } 615 | .fa-phone:before { 616 | content: "\f095"; 617 | } 618 | .fa-square-o:before { 619 | content: "\f096"; 620 | } 621 | .fa-bookmark-o:before { 622 | content: "\f097"; 623 | } 624 | .fa-phone-square:before { 625 | content: "\f098"; 626 | } 627 | .fa-twitter:before { 628 | content: "\f099"; 629 | } 630 | .fa-facebook-f:before, 631 | .fa-facebook:before { 632 | content: "\f09a"; 633 | } 634 | .fa-github:before { 635 | content: "\f09b"; 636 | } 637 | .fa-unlock:before { 638 | content: "\f09c"; 639 | } 640 | .fa-credit-card:before { 641 | content: "\f09d"; 642 | } 643 | .fa-feed:before, 644 | .fa-rss:before { 645 | content: "\f09e"; 646 | } 647 | .fa-hdd-o:before { 648 | content: "\f0a0"; 649 | } 650 | .fa-bullhorn:before { 651 | content: "\f0a1"; 652 | } 653 | .fa-bell:before { 654 | content: "\f0f3"; 655 | } 656 | .fa-certificate:before { 657 | content: "\f0a3"; 658 | } 659 | .fa-hand-o-right:before { 660 | content: "\f0a4"; 661 | } 662 | .fa-hand-o-left:before { 663 | content: "\f0a5"; 664 | } 665 | .fa-hand-o-up:before { 666 | content: "\f0a6"; 667 | } 668 | .fa-hand-o-down:before { 669 | content: "\f0a7"; 670 | } 671 | .fa-arrow-circle-left:before { 672 | content: "\f0a8"; 673 | } 674 | .fa-arrow-circle-right:before { 675 | content: "\f0a9"; 676 | } 677 | .fa-arrow-circle-up:before { 678 | content: "\f0aa"; 679 | } 680 | .fa-arrow-circle-down:before { 681 | content: "\f0ab"; 682 | } 683 | .fa-globe:before { 684 | content: "\f0ac"; 685 | } 686 | .fa-wrench:before { 687 | content: "\f0ad"; 688 | } 689 | .fa-tasks:before { 690 | content: "\f0ae"; 691 | } 692 | .fa-filter:before { 693 | content: "\f0b0"; 694 | } 695 | .fa-briefcase:before { 696 | content: "\f0b1"; 697 | } 698 | .fa-arrows-alt:before { 699 | content: "\f0b2"; 700 | } 701 | .fa-group:before, 702 | .fa-users:before { 703 | content: "\f0c0"; 704 | } 705 | .fa-chain:before, 706 | .fa-link:before { 707 | content: "\f0c1"; 708 | } 709 | .fa-cloud:before { 710 | content: "\f0c2"; 711 | } 712 | .fa-flask:before { 713 | content: "\f0c3"; 714 | } 715 | .fa-cut:before, 716 | .fa-scissors:before { 717 | content: "\f0c4"; 718 | } 719 | .fa-copy:before, 720 | .fa-files-o:before { 721 | content: "\f0c5"; 722 | } 723 | .fa-paperclip:before { 724 | content: "\f0c6"; 725 | } 726 | .fa-save:before, 727 | .fa-floppy-o:before { 728 | content: "\f0c7"; 729 | } 730 | .fa-square:before { 731 | content: "\f0c8"; 732 | } 733 | .fa-navicon:before, 734 | .fa-reorder:before, 735 | .fa-bars:before { 736 | content: "\f0c9"; 737 | } 738 | .fa-list-ul:before { 739 | content: "\f0ca"; 740 | } 741 | .fa-list-ol:before { 742 | content: "\f0cb"; 743 | } 744 | .fa-strikethrough:before { 745 | content: "\f0cc"; 746 | } 747 | .fa-underline:before { 748 | content: "\f0cd"; 749 | } 750 | .fa-table:before { 751 | content: "\f0ce"; 752 | } 753 | .fa-magic:before { 754 | content: "\f0d0"; 755 | } 756 | .fa-truck:before { 757 | content: "\f0d1"; 758 | } 759 | .fa-pinterest:before { 760 | content: "\f0d2"; 761 | } 762 | .fa-pinterest-square:before { 763 | content: "\f0d3"; 764 | } 765 | .fa-google-plus-square:before { 766 | content: "\f0d4"; 767 | } 768 | .fa-google-plus:before { 769 | content: "\f0d5"; 770 | } 771 | .fa-money:before { 772 | content: "\f0d6"; 773 | } 774 | .fa-caret-down:before { 775 | content: "\f0d7"; 776 | } 777 | .fa-caret-up:before { 778 | content: "\f0d8"; 779 | } 780 | .fa-caret-left:before { 781 | content: "\f0d9"; 782 | } 783 | .fa-caret-right:before { 784 | content: "\f0da"; 785 | } 786 | .fa-columns:before { 787 | content: "\f0db"; 788 | } 789 | .fa-unsorted:before, 790 | .fa-sort:before { 791 | content: "\f0dc"; 792 | } 793 | .fa-sort-down:before, 794 | .fa-sort-desc:before { 795 | content: "\f0dd"; 796 | } 797 | .fa-sort-up:before, 798 | .fa-sort-asc:before { 799 | content: "\f0de"; 800 | } 801 | .fa-envelope:before { 802 | content: "\f0e0"; 803 | } 804 | .fa-linkedin:before { 805 | content: "\f0e1"; 806 | } 807 | .fa-rotate-left:before, 808 | .fa-undo:before { 809 | content: "\f0e2"; 810 | } 811 | .fa-legal:before, 812 | .fa-gavel:before { 813 | content: "\f0e3"; 814 | } 815 | .fa-dashboard:before, 816 | .fa-tachometer:before { 817 | content: "\f0e4"; 818 | } 819 | .fa-comment-o:before { 820 | content: "\f0e5"; 821 | } 822 | .fa-comments-o:before { 823 | content: "\f0e6"; 824 | } 825 | .fa-flash:before, 826 | .fa-bolt:before { 827 | content: "\f0e7"; 828 | } 829 | .fa-sitemap:before { 830 | content: "\f0e8"; 831 | } 832 | .fa-umbrella:before { 833 | content: "\f0e9"; 834 | } 835 | .fa-paste:before, 836 | .fa-clipboard:before { 837 | content: "\f0ea"; 838 | } 839 | .fa-lightbulb-o:before { 840 | content: "\f0eb"; 841 | } 842 | .fa-exchange:before { 843 | content: "\f0ec"; 844 | } 845 | .fa-cloud-download:before { 846 | content: "\f0ed"; 847 | } 848 | .fa-cloud-upload:before { 849 | content: "\f0ee"; 850 | } 851 | .fa-user-md:before { 852 | content: "\f0f0"; 853 | } 854 | .fa-stethoscope:before { 855 | content: "\f0f1"; 856 | } 857 | .fa-suitcase:before { 858 | content: "\f0f2"; 859 | } 860 | .fa-bell-o:before { 861 | content: "\f0a2"; 862 | } 863 | .fa-coffee:before { 864 | content: "\f0f4"; 865 | } 866 | .fa-cutlery:before { 867 | content: "\f0f5"; 868 | } 869 | .fa-file-text-o:before { 870 | content: "\f0f6"; 871 | } 872 | .fa-building-o:before { 873 | content: "\f0f7"; 874 | } 875 | .fa-hospital-o:before { 876 | content: "\f0f8"; 877 | } 878 | .fa-ambulance:before { 879 | content: "\f0f9"; 880 | } 881 | .fa-medkit:before { 882 | content: "\f0fa"; 883 | } 884 | .fa-fighter-jet:before { 885 | content: "\f0fb"; 886 | } 887 | .fa-beer:before { 888 | content: "\f0fc"; 889 | } 890 | .fa-h-square:before { 891 | content: "\f0fd"; 892 | } 893 | .fa-plus-square:before { 894 | content: "\f0fe"; 895 | } 896 | .fa-angle-double-left:before { 897 | content: "\f100"; 898 | } 899 | .fa-angle-double-right:before { 900 | content: "\f101"; 901 | } 902 | .fa-angle-double-up:before { 903 | content: "\f102"; 904 | } 905 | .fa-angle-double-down:before { 906 | content: "\f103"; 907 | } 908 | .fa-angle-left:before { 909 | content: "\f104"; 910 | } 911 | .fa-angle-right:before { 912 | content: "\f105"; 913 | } 914 | .fa-angle-up:before { 915 | content: "\f106"; 916 | } 917 | .fa-angle-down:before { 918 | content: "\f107"; 919 | } 920 | .fa-desktop:before { 921 | content: "\f108"; 922 | } 923 | .fa-laptop:before { 924 | content: "\f109"; 925 | } 926 | .fa-tablet:before { 927 | content: "\f10a"; 928 | } 929 | .fa-mobile-phone:before, 930 | .fa-mobile:before { 931 | content: "\f10b"; 932 | } 933 | .fa-circle-o:before { 934 | content: "\f10c"; 935 | } 936 | .fa-quote-left:before { 937 | content: "\f10d"; 938 | } 939 | .fa-quote-right:before { 940 | content: "\f10e"; 941 | } 942 | .fa-spinner:before { 943 | content: "\f110"; 944 | } 945 | .fa-circle:before { 946 | content: "\f111"; 947 | } 948 | .fa-mail-reply:before, 949 | .fa-reply:before { 950 | content: "\f112"; 951 | } 952 | .fa-github-alt:before { 953 | content: "\f113"; 954 | } 955 | .fa-folder-o:before { 956 | content: "\f114"; 957 | } 958 | .fa-folder-open-o:before { 959 | content: "\f115"; 960 | } 961 | .fa-smile-o:before { 962 | content: "\f118"; 963 | } 964 | .fa-frown-o:before { 965 | content: "\f119"; 966 | } 967 | .fa-meh-o:before { 968 | content: "\f11a"; 969 | } 970 | .fa-gamepad:before { 971 | content: "\f11b"; 972 | } 973 | .fa-keyboard-o:before { 974 | content: "\f11c"; 975 | } 976 | .fa-flag-o:before { 977 | content: "\f11d"; 978 | } 979 | .fa-flag-checkered:before { 980 | content: "\f11e"; 981 | } 982 | .fa-terminal:before { 983 | content: "\f120"; 984 | } 985 | .fa-code:before { 986 | content: "\f121"; 987 | } 988 | .fa-mail-reply-all:before, 989 | .fa-reply-all:before { 990 | content: "\f122"; 991 | } 992 | .fa-star-half-empty:before, 993 | .fa-star-half-full:before, 994 | .fa-star-half-o:before { 995 | content: "\f123"; 996 | } 997 | .fa-location-arrow:before { 998 | content: "\f124"; 999 | } 1000 | .fa-crop:before { 1001 | content: "\f125"; 1002 | } 1003 | .fa-code-fork:before { 1004 | content: "\f126"; 1005 | } 1006 | .fa-unlink:before, 1007 | .fa-chain-broken:before { 1008 | content: "\f127"; 1009 | } 1010 | .fa-question:before { 1011 | content: "\f128"; 1012 | } 1013 | .fa-info:before { 1014 | content: "\f129"; 1015 | } 1016 | .fa-exclamation:before { 1017 | content: "\f12a"; 1018 | } 1019 | .fa-superscript:before { 1020 | content: "\f12b"; 1021 | } 1022 | .fa-subscript:before { 1023 | content: "\f12c"; 1024 | } 1025 | .fa-eraser:before { 1026 | content: "\f12d"; 1027 | } 1028 | .fa-puzzle-piece:before { 1029 | content: "\f12e"; 1030 | } 1031 | .fa-microphone:before { 1032 | content: "\f130"; 1033 | } 1034 | .fa-microphone-slash:before { 1035 | content: "\f131"; 1036 | } 1037 | .fa-shield:before { 1038 | content: "\f132"; 1039 | } 1040 | .fa-calendar-o:before { 1041 | content: "\f133"; 1042 | } 1043 | .fa-fire-extinguisher:before { 1044 | content: "\f134"; 1045 | } 1046 | .fa-rocket:before { 1047 | content: "\f135"; 1048 | } 1049 | .fa-maxcdn:before { 1050 | content: "\f136"; 1051 | } 1052 | .fa-chevron-circle-left:before { 1053 | content: "\f137"; 1054 | } 1055 | .fa-chevron-circle-right:before { 1056 | content: "\f138"; 1057 | } 1058 | .fa-chevron-circle-up:before { 1059 | content: "\f139"; 1060 | } 1061 | .fa-chevron-circle-down:before { 1062 | content: "\f13a"; 1063 | } 1064 | .fa-html5:before { 1065 | content: "\f13b"; 1066 | } 1067 | .fa-css3:before { 1068 | content: "\f13c"; 1069 | } 1070 | .fa-anchor:before { 1071 | content: "\f13d"; 1072 | } 1073 | .fa-unlock-alt:before { 1074 | content: "\f13e"; 1075 | } 1076 | .fa-bullseye:before { 1077 | content: "\f140"; 1078 | } 1079 | .fa-ellipsis-h:before { 1080 | content: "\f141"; 1081 | } 1082 | .fa-ellipsis-v:before { 1083 | content: "\f142"; 1084 | } 1085 | .fa-rss-square:before { 1086 | content: "\f143"; 1087 | } 1088 | .fa-play-circle:before { 1089 | content: "\f144"; 1090 | } 1091 | .fa-ticket:before { 1092 | content: "\f145"; 1093 | } 1094 | .fa-minus-square:before { 1095 | content: "\f146"; 1096 | } 1097 | .fa-minus-square-o:before { 1098 | content: "\f147"; 1099 | } 1100 | .fa-level-up:before { 1101 | content: "\f148"; 1102 | } 1103 | .fa-level-down:before { 1104 | content: "\f149"; 1105 | } 1106 | .fa-check-square:before { 1107 | content: "\f14a"; 1108 | } 1109 | .fa-pencil-square:before { 1110 | content: "\f14b"; 1111 | } 1112 | .fa-external-link-square:before { 1113 | content: "\f14c"; 1114 | } 1115 | .fa-share-square:before { 1116 | content: "\f14d"; 1117 | } 1118 | .fa-compass:before { 1119 | content: "\f14e"; 1120 | } 1121 | .fa-toggle-down:before, 1122 | .fa-caret-square-o-down:before { 1123 | content: "\f150"; 1124 | } 1125 | .fa-toggle-up:before, 1126 | .fa-caret-square-o-up:before { 1127 | content: "\f151"; 1128 | } 1129 | .fa-toggle-right:before, 1130 | .fa-caret-square-o-right:before { 1131 | content: "\f152"; 1132 | } 1133 | .fa-euro:before, 1134 | .fa-eur:before { 1135 | content: "\f153"; 1136 | } 1137 | .fa-gbp:before { 1138 | content: "\f154"; 1139 | } 1140 | .fa-dollar:before, 1141 | .fa-usd:before { 1142 | content: "\f155"; 1143 | } 1144 | .fa-rupee:before, 1145 | .fa-inr:before { 1146 | content: "\f156"; 1147 | } 1148 | .fa-cny:before, 1149 | .fa-rmb:before, 1150 | .fa-yen:before, 1151 | .fa-jpy:before { 1152 | content: "\f157"; 1153 | } 1154 | .fa-ruble:before, 1155 | .fa-rouble:before, 1156 | .fa-rub:before { 1157 | content: "\f158"; 1158 | } 1159 | .fa-won:before, 1160 | .fa-krw:before { 1161 | content: "\f159"; 1162 | } 1163 | .fa-bitcoin:before, 1164 | .fa-btc:before { 1165 | content: "\f15a"; 1166 | } 1167 | .fa-file:before { 1168 | content: "\f15b"; 1169 | } 1170 | .fa-file-text:before { 1171 | content: "\f15c"; 1172 | } 1173 | .fa-sort-alpha-asc:before { 1174 | content: "\f15d"; 1175 | } 1176 | .fa-sort-alpha-desc:before { 1177 | content: "\f15e"; 1178 | } 1179 | .fa-sort-amount-asc:before { 1180 | content: "\f160"; 1181 | } 1182 | .fa-sort-amount-desc:before { 1183 | content: "\f161"; 1184 | } 1185 | .fa-sort-numeric-asc:before { 1186 | content: "\f162"; 1187 | } 1188 | .fa-sort-numeric-desc:before { 1189 | content: "\f163"; 1190 | } 1191 | .fa-thumbs-up:before { 1192 | content: "\f164"; 1193 | } 1194 | .fa-thumbs-down:before { 1195 | content: "\f165"; 1196 | } 1197 | .fa-youtube-square:before { 1198 | content: "\f166"; 1199 | } 1200 | .fa-youtube:before { 1201 | content: "\f167"; 1202 | } 1203 | .fa-xing:before { 1204 | content: "\f168"; 1205 | } 1206 | .fa-xing-square:before { 1207 | content: "\f169"; 1208 | } 1209 | .fa-youtube-play:before { 1210 | content: "\f16a"; 1211 | } 1212 | .fa-dropbox:before { 1213 | content: "\f16b"; 1214 | } 1215 | .fa-stack-overflow:before { 1216 | content: "\f16c"; 1217 | } 1218 | .fa-instagram:before { 1219 | content: "\f16d"; 1220 | } 1221 | .fa-flickr:before { 1222 | content: "\f16e"; 1223 | } 1224 | .fa-adn:before { 1225 | content: "\f170"; 1226 | } 1227 | .fa-bitbucket:before { 1228 | content: "\f171"; 1229 | } 1230 | .fa-bitbucket-square:before { 1231 | content: "\f172"; 1232 | } 1233 | .fa-tumblr:before { 1234 | content: "\f173"; 1235 | } 1236 | .fa-tumblr-square:before { 1237 | content: "\f174"; 1238 | } 1239 | .fa-long-arrow-down:before { 1240 | content: "\f175"; 1241 | } 1242 | .fa-long-arrow-up:before { 1243 | content: "\f176"; 1244 | } 1245 | .fa-long-arrow-left:before { 1246 | content: "\f177"; 1247 | } 1248 | .fa-long-arrow-right:before { 1249 | content: "\f178"; 1250 | } 1251 | .fa-apple:before { 1252 | content: "\f179"; 1253 | } 1254 | .fa-windows:before { 1255 | content: "\f17a"; 1256 | } 1257 | .fa-android:before { 1258 | content: "\f17b"; 1259 | } 1260 | .fa-linux:before { 1261 | content: "\f17c"; 1262 | } 1263 | .fa-dribbble:before { 1264 | content: "\f17d"; 1265 | } 1266 | .fa-skype:before { 1267 | content: "\f17e"; 1268 | } 1269 | .fa-foursquare:before { 1270 | content: "\f180"; 1271 | } 1272 | .fa-trello:before { 1273 | content: "\f181"; 1274 | } 1275 | .fa-female:before { 1276 | content: "\f182"; 1277 | } 1278 | .fa-male:before { 1279 | content: "\f183"; 1280 | } 1281 | .fa-gittip:before, 1282 | .fa-gratipay:before { 1283 | content: "\f184"; 1284 | } 1285 | .fa-sun-o:before { 1286 | content: "\f185"; 1287 | } 1288 | .fa-moon-o:before { 1289 | content: "\f186"; 1290 | } 1291 | .fa-archive:before { 1292 | content: "\f187"; 1293 | } 1294 | .fa-bug:before { 1295 | content: "\f188"; 1296 | } 1297 | .fa-vk:before { 1298 | content: "\f189"; 1299 | } 1300 | .fa-weibo:before { 1301 | content: "\f18a"; 1302 | } 1303 | .fa-renren:before { 1304 | content: "\f18b"; 1305 | } 1306 | .fa-pagelines:before { 1307 | content: "\f18c"; 1308 | } 1309 | .fa-stack-exchange:before { 1310 | content: "\f18d"; 1311 | } 1312 | .fa-arrow-circle-o-right:before { 1313 | content: "\f18e"; 1314 | } 1315 | .fa-arrow-circle-o-left:before { 1316 | content: "\f190"; 1317 | } 1318 | .fa-toggle-left:before, 1319 | .fa-caret-square-o-left:before { 1320 | content: "\f191"; 1321 | } 1322 | .fa-dot-circle-o:before { 1323 | content: "\f192"; 1324 | } 1325 | .fa-wheelchair:before { 1326 | content: "\f193"; 1327 | } 1328 | .fa-vimeo-square:before { 1329 | content: "\f194"; 1330 | } 1331 | .fa-turkish-lira:before, 1332 | .fa-try:before { 1333 | content: "\f195"; 1334 | } 1335 | .fa-plus-square-o:before { 1336 | content: "\f196"; 1337 | } 1338 | .fa-space-shuttle:before { 1339 | content: "\f197"; 1340 | } 1341 | .fa-slack:before { 1342 | content: "\f198"; 1343 | } 1344 | .fa-envelope-square:before { 1345 | content: "\f199"; 1346 | } 1347 | .fa-wordpress:before { 1348 | content: "\f19a"; 1349 | } 1350 | .fa-openid:before { 1351 | content: "\f19b"; 1352 | } 1353 | .fa-institution:before, 1354 | .fa-bank:before, 1355 | .fa-university:before { 1356 | content: "\f19c"; 1357 | } 1358 | .fa-mortar-board:before, 1359 | .fa-graduation-cap:before { 1360 | content: "\f19d"; 1361 | } 1362 | .fa-yahoo:before { 1363 | content: "\f19e"; 1364 | } 1365 | .fa-google:before { 1366 | content: "\f1a0"; 1367 | } 1368 | .fa-reddit:before { 1369 | content: "\f1a1"; 1370 | } 1371 | .fa-reddit-square:before { 1372 | content: "\f1a2"; 1373 | } 1374 | .fa-stumbleupon-circle:before { 1375 | content: "\f1a3"; 1376 | } 1377 | .fa-stumbleupon:before { 1378 | content: "\f1a4"; 1379 | } 1380 | .fa-delicious:before { 1381 | content: "\f1a5"; 1382 | } 1383 | .fa-digg:before { 1384 | content: "\f1a6"; 1385 | } 1386 | .fa-pied-piper:before { 1387 | content: "\f1a7"; 1388 | } 1389 | .fa-pied-piper-alt:before { 1390 | content: "\f1a8"; 1391 | } 1392 | .fa-drupal:before { 1393 | content: "\f1a9"; 1394 | } 1395 | .fa-joomla:before { 1396 | content: "\f1aa"; 1397 | } 1398 | .fa-language:before { 1399 | content: "\f1ab"; 1400 | } 1401 | .fa-fax:before { 1402 | content: "\f1ac"; 1403 | } 1404 | .fa-building:before { 1405 | content: "\f1ad"; 1406 | } 1407 | .fa-child:before { 1408 | content: "\f1ae"; 1409 | } 1410 | .fa-paw:before { 1411 | content: "\f1b0"; 1412 | } 1413 | .fa-spoon:before { 1414 | content: "\f1b1"; 1415 | } 1416 | .fa-cube:before { 1417 | content: "\f1b2"; 1418 | } 1419 | .fa-cubes:before { 1420 | content: "\f1b3"; 1421 | } 1422 | .fa-behance:before { 1423 | content: "\f1b4"; 1424 | } 1425 | .fa-behance-square:before { 1426 | content: "\f1b5"; 1427 | } 1428 | .fa-steam:before { 1429 | content: "\f1b6"; 1430 | } 1431 | .fa-steam-square:before { 1432 | content: "\f1b7"; 1433 | } 1434 | .fa-recycle:before { 1435 | content: "\f1b8"; 1436 | } 1437 | .fa-automobile:before, 1438 | .fa-car:before { 1439 | content: "\f1b9"; 1440 | } 1441 | .fa-cab:before, 1442 | .fa-taxi:before { 1443 | content: "\f1ba"; 1444 | } 1445 | .fa-tree:before { 1446 | content: "\f1bb"; 1447 | } 1448 | .fa-spotify:before { 1449 | content: "\f1bc"; 1450 | } 1451 | .fa-deviantart:before { 1452 | content: "\f1bd"; 1453 | } 1454 | .fa-soundcloud:before { 1455 | content: "\f1be"; 1456 | } 1457 | .fa-database:before { 1458 | content: "\f1c0"; 1459 | } 1460 | .fa-file-pdf-o:before { 1461 | content: "\f1c1"; 1462 | } 1463 | .fa-file-word-o:before { 1464 | content: "\f1c2"; 1465 | } 1466 | .fa-file-excel-o:before { 1467 | content: "\f1c3"; 1468 | } 1469 | .fa-file-powerpoint-o:before { 1470 | content: "\f1c4"; 1471 | } 1472 | .fa-file-photo-o:before, 1473 | .fa-file-picture-o:before, 1474 | .fa-file-image-o:before { 1475 | content: "\f1c5"; 1476 | } 1477 | .fa-file-zip-o:before, 1478 | .fa-file-archive-o:before { 1479 | content: "\f1c6"; 1480 | } 1481 | .fa-file-sound-o:before, 1482 | .fa-file-audio-o:before { 1483 | content: "\f1c7"; 1484 | } 1485 | .fa-file-movie-o:before, 1486 | .fa-file-video-o:before { 1487 | content: "\f1c8"; 1488 | } 1489 | .fa-file-code-o:before { 1490 | content: "\f1c9"; 1491 | } 1492 | .fa-vine:before { 1493 | content: "\f1ca"; 1494 | } 1495 | .fa-codepen:before { 1496 | content: "\f1cb"; 1497 | } 1498 | .fa-jsfiddle:before { 1499 | content: "\f1cc"; 1500 | } 1501 | .fa-life-bouy:before, 1502 | .fa-life-buoy:before, 1503 | .fa-life-saver:before, 1504 | .fa-support:before, 1505 | .fa-life-ring:before { 1506 | content: "\f1cd"; 1507 | } 1508 | .fa-circle-o-notch:before { 1509 | content: "\f1ce"; 1510 | } 1511 | .fa-ra:before, 1512 | .fa-rebel:before { 1513 | content: "\f1d0"; 1514 | } 1515 | .fa-ge:before, 1516 | .fa-empire:before { 1517 | content: "\f1d1"; 1518 | } 1519 | .fa-git-square:before { 1520 | content: "\f1d2"; 1521 | } 1522 | .fa-git:before { 1523 | content: "\f1d3"; 1524 | } 1525 | .fa-y-combinator-square:before, 1526 | .fa-yc-square:before, 1527 | .fa-hacker-news:before { 1528 | content: "\f1d4"; 1529 | } 1530 | .fa-tencent-weibo:before { 1531 | content: "\f1d5"; 1532 | } 1533 | .fa-qq:before { 1534 | content: "\f1d6"; 1535 | } 1536 | .fa-wechat:before, 1537 | .fa-weixin:before { 1538 | content: "\f1d7"; 1539 | } 1540 | .fa-send:before, 1541 | .fa-paper-plane:before { 1542 | content: "\f1d8"; 1543 | } 1544 | .fa-send-o:before, 1545 | .fa-paper-plane-o:before { 1546 | content: "\f1d9"; 1547 | } 1548 | .fa-history:before { 1549 | content: "\f1da"; 1550 | } 1551 | .fa-circle-thin:before { 1552 | content: "\f1db"; 1553 | } 1554 | .fa-header:before { 1555 | content: "\f1dc"; 1556 | } 1557 | .fa-paragraph:before { 1558 | content: "\f1dd"; 1559 | } 1560 | .fa-sliders:before { 1561 | content: "\f1de"; 1562 | } 1563 | .fa-share-alt:before { 1564 | content: "\f1e0"; 1565 | } 1566 | .fa-share-alt-square:before { 1567 | content: "\f1e1"; 1568 | } 1569 | .fa-bomb:before { 1570 | content: "\f1e2"; 1571 | } 1572 | .fa-soccer-ball-o:before, 1573 | .fa-futbol-o:before { 1574 | content: "\f1e3"; 1575 | } 1576 | .fa-tty:before { 1577 | content: "\f1e4"; 1578 | } 1579 | .fa-binoculars:before { 1580 | content: "\f1e5"; 1581 | } 1582 | .fa-plug:before { 1583 | content: "\f1e6"; 1584 | } 1585 | .fa-slideshare:before { 1586 | content: "\f1e7"; 1587 | } 1588 | .fa-twitch:before { 1589 | content: "\f1e8"; 1590 | } 1591 | .fa-yelp:before { 1592 | content: "\f1e9"; 1593 | } 1594 | .fa-newspaper-o:before { 1595 | content: "\f1ea"; 1596 | } 1597 | .fa-wifi:before { 1598 | content: "\f1eb"; 1599 | } 1600 | .fa-calculator:before { 1601 | content: "\f1ec"; 1602 | } 1603 | .fa-paypal:before { 1604 | content: "\f1ed"; 1605 | } 1606 | .fa-google-wallet:before { 1607 | content: "\f1ee"; 1608 | } 1609 | .fa-cc-visa:before { 1610 | content: "\f1f0"; 1611 | } 1612 | .fa-cc-mastercard:before { 1613 | content: "\f1f1"; 1614 | } 1615 | .fa-cc-discover:before { 1616 | content: "\f1f2"; 1617 | } 1618 | .fa-cc-amex:before { 1619 | content: "\f1f3"; 1620 | } 1621 | .fa-cc-paypal:before { 1622 | content: "\f1f4"; 1623 | } 1624 | .fa-cc-stripe:before { 1625 | content: "\f1f5"; 1626 | } 1627 | .fa-bell-slash:before { 1628 | content: "\f1f6"; 1629 | } 1630 | .fa-bell-slash-o:before { 1631 | content: "\f1f7"; 1632 | } 1633 | .fa-trash:before { 1634 | content: "\f1f8"; 1635 | } 1636 | .fa-copyright:before { 1637 | content: "\f1f9"; 1638 | } 1639 | .fa-at:before { 1640 | content: "\f1fa"; 1641 | } 1642 | .fa-eyedropper:before { 1643 | content: "\f1fb"; 1644 | } 1645 | .fa-paint-brush:before { 1646 | content: "\f1fc"; 1647 | } 1648 | .fa-birthday-cake:before { 1649 | content: "\f1fd"; 1650 | } 1651 | .fa-area-chart:before { 1652 | content: "\f1fe"; 1653 | } 1654 | .fa-pie-chart:before { 1655 | content: "\f200"; 1656 | } 1657 | .fa-line-chart:before { 1658 | content: "\f201"; 1659 | } 1660 | .fa-lastfm:before { 1661 | content: "\f202"; 1662 | } 1663 | .fa-lastfm-square:before { 1664 | content: "\f203"; 1665 | } 1666 | .fa-toggle-off:before { 1667 | content: "\f204"; 1668 | } 1669 | .fa-toggle-on:before { 1670 | content: "\f205"; 1671 | } 1672 | .fa-bicycle:before { 1673 | content: "\f206"; 1674 | } 1675 | .fa-bus:before { 1676 | content: "\f207"; 1677 | } 1678 | .fa-ioxhost:before { 1679 | content: "\f208"; 1680 | } 1681 | .fa-angellist:before { 1682 | content: "\f209"; 1683 | } 1684 | .fa-cc:before { 1685 | content: "\f20a"; 1686 | } 1687 | .fa-shekel:before, 1688 | .fa-sheqel:before, 1689 | .fa-ils:before { 1690 | content: "\f20b"; 1691 | } 1692 | .fa-meanpath:before { 1693 | content: "\f20c"; 1694 | } 1695 | .fa-buysellads:before { 1696 | content: "\f20d"; 1697 | } 1698 | .fa-connectdevelop:before { 1699 | content: "\f20e"; 1700 | } 1701 | .fa-dashcube:before { 1702 | content: "\f210"; 1703 | } 1704 | .fa-forumbee:before { 1705 | content: "\f211"; 1706 | } 1707 | .fa-leanpub:before { 1708 | content: "\f212"; 1709 | } 1710 | .fa-sellsy:before { 1711 | content: "\f213"; 1712 | } 1713 | .fa-shirtsinbulk:before { 1714 | content: "\f214"; 1715 | } 1716 | .fa-simplybuilt:before { 1717 | content: "\f215"; 1718 | } 1719 | .fa-skyatlas:before { 1720 | content: "\f216"; 1721 | } 1722 | .fa-cart-plus:before { 1723 | content: "\f217"; 1724 | } 1725 | .fa-cart-arrow-down:before { 1726 | content: "\f218"; 1727 | } 1728 | .fa-diamond:before { 1729 | content: "\f219"; 1730 | } 1731 | .fa-ship:before { 1732 | content: "\f21a"; 1733 | } 1734 | .fa-user-secret:before { 1735 | content: "\f21b"; 1736 | } 1737 | .fa-motorcycle:before { 1738 | content: "\f21c"; 1739 | } 1740 | .fa-street-view:before { 1741 | content: "\f21d"; 1742 | } 1743 | .fa-heartbeat:before { 1744 | content: "\f21e"; 1745 | } 1746 | .fa-venus:before { 1747 | content: "\f221"; 1748 | } 1749 | .fa-mars:before { 1750 | content: "\f222"; 1751 | } 1752 | .fa-mercury:before { 1753 | content: "\f223"; 1754 | } 1755 | .fa-intersex:before, 1756 | .fa-transgender:before { 1757 | content: "\f224"; 1758 | } 1759 | .fa-transgender-alt:before { 1760 | content: "\f225"; 1761 | } 1762 | .fa-venus-double:before { 1763 | content: "\f226"; 1764 | } 1765 | .fa-mars-double:before { 1766 | content: "\f227"; 1767 | } 1768 | .fa-venus-mars:before { 1769 | content: "\f228"; 1770 | } 1771 | .fa-mars-stroke:before { 1772 | content: "\f229"; 1773 | } 1774 | .fa-mars-stroke-v:before { 1775 | content: "\f22a"; 1776 | } 1777 | .fa-mars-stroke-h:before { 1778 | content: "\f22b"; 1779 | } 1780 | .fa-neuter:before { 1781 | content: "\f22c"; 1782 | } 1783 | .fa-genderless:before { 1784 | content: "\f22d"; 1785 | } 1786 | .fa-facebook-official:before { 1787 | content: "\f230"; 1788 | } 1789 | .fa-pinterest-p:before { 1790 | content: "\f231"; 1791 | } 1792 | .fa-whatsapp:before { 1793 | content: "\f232"; 1794 | } 1795 | .fa-server:before { 1796 | content: "\f233"; 1797 | } 1798 | .fa-user-plus:before { 1799 | content: "\f234"; 1800 | } 1801 | .fa-user-times:before { 1802 | content: "\f235"; 1803 | } 1804 | .fa-hotel:before, 1805 | .fa-bed:before { 1806 | content: "\f236"; 1807 | } 1808 | .fa-viacoin:before { 1809 | content: "\f237"; 1810 | } 1811 | .fa-train:before { 1812 | content: "\f238"; 1813 | } 1814 | .fa-subway:before { 1815 | content: "\f239"; 1816 | } 1817 | .fa-medium:before { 1818 | content: "\f23a"; 1819 | } 1820 | .fa-yc:before, 1821 | .fa-y-combinator:before { 1822 | content: "\f23b"; 1823 | } 1824 | .fa-optin-monster:before { 1825 | content: "\f23c"; 1826 | } 1827 | .fa-opencart:before { 1828 | content: "\f23d"; 1829 | } 1830 | .fa-expeditedssl:before { 1831 | content: "\f23e"; 1832 | } 1833 | .fa-battery-4:before, 1834 | .fa-battery-full:before { 1835 | content: "\f240"; 1836 | } 1837 | .fa-battery-3:before, 1838 | .fa-battery-three-quarters:before { 1839 | content: "\f241"; 1840 | } 1841 | .fa-battery-2:before, 1842 | .fa-battery-half:before { 1843 | content: "\f242"; 1844 | } 1845 | .fa-battery-1:before, 1846 | .fa-battery-quarter:before { 1847 | content: "\f243"; 1848 | } 1849 | .fa-battery-0:before, 1850 | .fa-battery-empty:before { 1851 | content: "\f244"; 1852 | } 1853 | .fa-mouse-pointer:before { 1854 | content: "\f245"; 1855 | } 1856 | .fa-i-cursor:before { 1857 | content: "\f246"; 1858 | } 1859 | .fa-object-group:before { 1860 | content: "\f247"; 1861 | } 1862 | .fa-object-ungroup:before { 1863 | content: "\f248"; 1864 | } 1865 | .fa-sticky-note:before { 1866 | content: "\f249"; 1867 | } 1868 | .fa-sticky-note-o:before { 1869 | content: "\f24a"; 1870 | } 1871 | .fa-cc-jcb:before { 1872 | content: "\f24b"; 1873 | } 1874 | .fa-cc-diners-club:before { 1875 | content: "\f24c"; 1876 | } 1877 | .fa-clone:before { 1878 | content: "\f24d"; 1879 | } 1880 | .fa-balance-scale:before { 1881 | content: "\f24e"; 1882 | } 1883 | .fa-hourglass-o:before { 1884 | content: "\f250"; 1885 | } 1886 | .fa-hourglass-1:before, 1887 | .fa-hourglass-start:before { 1888 | content: "\f251"; 1889 | } 1890 | .fa-hourglass-2:before, 1891 | .fa-hourglass-half:before { 1892 | content: "\f252"; 1893 | } 1894 | .fa-hourglass-3:before, 1895 | .fa-hourglass-end:before { 1896 | content: "\f253"; 1897 | } 1898 | .fa-hourglass:before { 1899 | content: "\f254"; 1900 | } 1901 | .fa-hand-grab-o:before, 1902 | .fa-hand-rock-o:before { 1903 | content: "\f255"; 1904 | } 1905 | .fa-hand-stop-o:before, 1906 | .fa-hand-paper-o:before { 1907 | content: "\f256"; 1908 | } 1909 | .fa-hand-scissors-o:before { 1910 | content: "\f257"; 1911 | } 1912 | .fa-hand-lizard-o:before { 1913 | content: "\f258"; 1914 | } 1915 | .fa-hand-spock-o:before { 1916 | content: "\f259"; 1917 | } 1918 | .fa-hand-pointer-o:before { 1919 | content: "\f25a"; 1920 | } 1921 | .fa-hand-peace-o:before { 1922 | content: "\f25b"; 1923 | } 1924 | .fa-trademark:before { 1925 | content: "\f25c"; 1926 | } 1927 | .fa-registered:before { 1928 | content: "\f25d"; 1929 | } 1930 | .fa-creative-commons:before { 1931 | content: "\f25e"; 1932 | } 1933 | .fa-gg:before { 1934 | content: "\f260"; 1935 | } 1936 | .fa-gg-circle:before { 1937 | content: "\f261"; 1938 | } 1939 | .fa-tripadvisor:before { 1940 | content: "\f262"; 1941 | } 1942 | .fa-odnoklassniki:before { 1943 | content: "\f263"; 1944 | } 1945 | .fa-odnoklassniki-square:before { 1946 | content: "\f264"; 1947 | } 1948 | .fa-get-pocket:before { 1949 | content: "\f265"; 1950 | } 1951 | .fa-wikipedia-w:before { 1952 | content: "\f266"; 1953 | } 1954 | .fa-safari:before { 1955 | content: "\f267"; 1956 | } 1957 | .fa-chrome:before { 1958 | content: "\f268"; 1959 | } 1960 | .fa-firefox:before { 1961 | content: "\f269"; 1962 | } 1963 | .fa-opera:before { 1964 | content: "\f26a"; 1965 | } 1966 | .fa-internet-explorer:before { 1967 | content: "\f26b"; 1968 | } 1969 | .fa-tv:before, 1970 | .fa-television:before { 1971 | content: "\f26c"; 1972 | } 1973 | .fa-contao:before { 1974 | content: "\f26d"; 1975 | } 1976 | .fa-500px:before { 1977 | content: "\f26e"; 1978 | } 1979 | .fa-amazon:before { 1980 | content: "\f270"; 1981 | } 1982 | .fa-calendar-plus-o:before { 1983 | content: "\f271"; 1984 | } 1985 | .fa-calendar-minus-o:before { 1986 | content: "\f272"; 1987 | } 1988 | .fa-calendar-times-o:before { 1989 | content: "\f273"; 1990 | } 1991 | .fa-calendar-check-o:before { 1992 | content: "\f274"; 1993 | } 1994 | .fa-industry:before { 1995 | content: "\f275"; 1996 | } 1997 | .fa-map-pin:before { 1998 | content: "\f276"; 1999 | } 2000 | .fa-map-signs:before { 2001 | content: "\f277"; 2002 | } 2003 | .fa-map-o:before { 2004 | content: "\f278"; 2005 | } 2006 | .fa-map:before { 2007 | content: "\f279"; 2008 | } 2009 | .fa-commenting:before { 2010 | content: "\f27a"; 2011 | } 2012 | .fa-commenting-o:before { 2013 | content: "\f27b"; 2014 | } 2015 | .fa-houzz:before { 2016 | content: "\f27c"; 2017 | } 2018 | .fa-vimeo:before { 2019 | content: "\f27d"; 2020 | } 2021 | .fa-black-tie:before { 2022 | content: "\f27e"; 2023 | } 2024 | .fa-fonticons:before { 2025 | content: "\f280"; 2026 | } 2027 | -------------------------------------------------------------------------------- /src/main/resources/static/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | body,html,#listGroupManage,.fadeInRight{ 3 | height:100%; 4 | width:100%; 5 | } 6 | 7 | body { 8 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | font-size: 14px; 10 | line-height: 1.428571429; 11 | color: #333333; 12 | } 13 | 14 | #chart-container { 15 | position: relative; 16 | display: inline-block; 17 | top: 10px; 18 | left: 10px; 19 | height: 420px; 20 | width: calc(100% - 24px); 21 | border: 2px dashed #aaa; 22 | border-radius: 5px; 23 | overflow: auto; 24 | text-align: center; 25 | } 26 | 27 | #chart-container { 28 | background-color: #eee; 29 | height: calc(100% - 24px); 30 | } 31 | 32 | .orgchart { 33 | background: #eee; 34 | } 35 | 36 | .edge { 37 | display: none; 38 | } 39 | 40 | .orgchart .node { 41 | width: 150px; 42 | } 43 | 44 | #contextDiv { 45 | display: none; 46 | position:absolute; 47 | } -------------------------------------------------------------------------------- /src/main/resources/static/dist/css/jquery.orgchart.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery OrgChart Plugin 3 | * https://github.com/dabeng/OrgChart 4 | * 5 | * Demos of jQuery OrgChart Plugin 6 | * http://dabeng.github.io/OrgChart/local-datasource/ 7 | * http://dabeng.github.io/OrgChart/ajax-datasource/ 8 | * http://dabeng.github.io/OrgChart/ondemand-loading-data/ 9 | * http://dabeng.github.io/OrgChart/option-createNode/ 10 | * http://dabeng.github.io/OrgChart/export-orgchart/ 11 | * http://dabeng.github.io/OrgChart/integrate-map/ 12 | * 13 | * Copyright 2016, dabeng 14 | * http://dabeng.github.io/ 15 | * 16 | * Licensed under the MIT license: 17 | * http://www.opensource.org/licenses/MIT 18 | */ 19 | 20 | .orgchart { 21 | display: inline-block; 22 | min-height: 202px; 23 | min-width: 202px; 24 | -webkit-touch-callout: none; 25 | -webkit-user-select: none; 26 | -khtml-user-select: none; 27 | -moz-user-select: none; 28 | -ms-user-select: none; 29 | user-select: none; 30 | background-image: linear-gradient(90deg, rgba(200, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%), linear-gradient(rgba(200, 0, 0, 0.15) 10%, rgba(0, 0, 0, 0) 10%); 31 | background-size: 10px 10px; 32 | border: 1px dashed rgba(0,0,0,0); 33 | padding: 20px; 34 | } 35 | 36 | .orgchart .hidden, .orgchart~.hidden { 37 | display: none!important; 38 | } 39 | 40 | .orgchart *, .orgchart *:before, .orgchart *:after { 41 | -webkit-box-sizing: border-box; 42 | -moz-box-sizing: border-box; 43 | box-sizing: border-box; 44 | } 45 | 46 | .orgchart.b2t { 47 | transform: rotate(180deg); 48 | -ms-transform: rotate(180deg); 49 | -moz-transform: rotate(180deg); 50 | -webkit-transform: rotate(180deg); 51 | } 52 | 53 | .orgchart.l2r { 54 | position: absolute; 55 | transform: rotate(-90deg) rotateY(180deg); 56 | -ms-transform: rotate(-90deg) rotateY(180deg); 57 | -moz-transform: rotate(-90deg) rotateY(180deg); 58 | -webkit-transform: rotate(-90deg) rotateY(180deg); 59 | transform-origin: left top; 60 | -ms-transform-origin: left top; 61 | -moz-transform-origin: left top; 62 | -webkit-transform-origin: left top; 63 | } 64 | 65 | .orgchart .verticalNodes ul { 66 | list-style: none; 67 | margin:0px; 68 | padding-left: 18px; 69 | text-align: left; 70 | } 71 | .orgchart .verticalNodes ul:first-child { 72 | margin-top: 2px; 73 | } 74 | .orgchart .verticalNodes>td::before { 75 | content: ''; 76 | border: 1px solid rgba(217, 83, 79, 0.8); 77 | } 78 | .orgchart .verticalNodes>td>ul>li:first-child::before { 79 | top: -4px; 80 | height: 30px; 81 | width: calc(50% - 2px); 82 | border-width: 2px 0 0 2px; 83 | } 84 | .orgchart .verticalNodes ul>li { 85 | position: relative; 86 | } 87 | .orgchart .verticalNodes ul>li::before, 88 | .orgchart .verticalNodes ul>li::after { 89 | content: ''; 90 | position: absolute; 91 | left: -6px; 92 | border-color: rgba(217, 83, 79, 0.8); 93 | border-style: solid; 94 | border-width: 0 0 2px 2px; 95 | } 96 | .orgchart .verticalNodes ul>li::before { 97 | top: -4px; 98 | height: 30px; 99 | width: 11px; 100 | } 101 | .orgchart .verticalNodes ul>li::after { 102 | top: 1px; 103 | height: 100%; 104 | } 105 | .orgchart .verticalNodes ul>li:first-child::after { 106 | top: 24px; 107 | width: 11px; 108 | border-width: 2px 0 0 2px; 109 | } 110 | .orgchart .verticalNodes ul>li:last-child::after { 111 | border-width: 2px 0 0; 112 | } 113 | 114 | .orgchart.r2l { 115 | position: absolute; 116 | transform: rotate(90deg); 117 | -ms-transform: rotate(90deg); 118 | -moz-transform: rotate(90deg); 119 | -webkit-transform: rotate(90deg); 120 | transform-origin: left top; 121 | -ms-transform-origin: left top; 122 | -moz-transform-origin: left top; 123 | -webkit-transform-origin: left top; 124 | } 125 | 126 | .orgchart>.spinner { 127 | font-size: 100px; 128 | margin-top: 30px; 129 | color: rgba(68, 157, 68, 0.8); 130 | } 131 | 132 | .orgchart table { 133 | border-spacing: 0!important; 134 | border-collapse: separate!important; 135 | } 136 | 137 | .orgchart>table:first-child{ 138 | margin: 20px auto; 139 | } 140 | 141 | .orgchart td { 142 | text-align: center; 143 | vertical-align: top; 144 | padding: 0; 145 | } 146 | 147 | .orgchart tr.lines td.topLine { 148 | border-top: 2px solid rgba(217, 83, 79, 0.8); 149 | } 150 | 151 | .orgchart tr.lines td.rightLine { 152 | border-right: 1px solid rgba(217, 83, 79, 0.8); 153 | float: none; 154 | border-radius: 0px; 155 | } 156 | 157 | .orgchart tr.lines td.leftLine { 158 | border-left: 1px solid rgba(217, 83, 79, 0.8); 159 | float: none; 160 | border-radius: 0px; 161 | } 162 | 163 | .orgchart tr.lines .downLine { 164 | background-color: rgba(217, 83, 79, 0.8); 165 | margin: 0px auto; 166 | height: 20px; 167 | width: 2px; 168 | float: none; 169 | } 170 | 171 | /* node styling */ 172 | .orgchart .node { 173 | display: inline-block; 174 | position: relative; 175 | margin: 0; 176 | padding: 3px; 177 | border: 2px dashed transparent; 178 | text-align: center; 179 | width: 130px; 180 | } 181 | 182 | .orgchart.l2r .node, .orgchart.r2l .node { 183 | width: 50px; 184 | height: 130px; 185 | } 186 | 187 | .orgchart .node>.spinner { 188 | position: absolute; 189 | top: calc(50% - 15px); 190 | left: calc(50% - 15px); 191 | vertical-align: middle; 192 | font-size: 30px; 193 | color: rgba(68, 157, 68, 0.8); 194 | } 195 | 196 | .orgchart .node:hover { 197 | background-color: rgba(238, 217, 54, 0.5); 198 | transition: .5s; 199 | cursor: default; 200 | z-index: 20; 201 | } 202 | 203 | .orgchart .node.focused { 204 | background-color: rgba(238, 217, 54, 0.5); 205 | } 206 | 207 | .orgchart .ghost-node { 208 | position: fixed; 209 | left: -10000px; 210 | top: -10000px; 211 | } 212 | 213 | .orgchart .ghost-node>* { 214 | fill: #ffffff; 215 | stroke: #bf0000; 216 | } 217 | 218 | .orgchart .node.allowedDrop { 219 | border-color: rgba(68, 157, 68, 0.9); 220 | } 221 | 222 | .orgchart .node .title { 223 | text-align: center; 224 | font-size: 12px; 225 | font-weight: bold; 226 | height: 20px; 227 | line-height: 20px; 228 | overflow: hidden; 229 | text-overflow: ellipsis; 230 | white-space: nowrap; 231 | background-color: rgba(217, 83, 79, 0.8); 232 | color: #fff; 233 | border-radius: 4px 4px 0 0; 234 | } 235 | 236 | .orgchart.b2t .node .title { 237 | transform: rotate(-180deg); 238 | -ms-transform: rotate(-180deg); 239 | -moz-transform: rotate(-180deg); 240 | -webkit-transform: rotate(-180deg); 241 | transform-origin: center bottom; 242 | -ms-transform-origin: center bottom; 243 | -moz-transform-origin: center bottom; 244 | -webkit-transform-origin: center bottom; 245 | } 246 | 247 | .orgchart.l2r .node .title { 248 | transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 249 | -ms-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 250 | -moz-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 251 | -webkit-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 252 | transform-origin: bottom center; 253 | -ms-transform-origin: bottom center; 254 | -moz-transform-origin: bottom center; 255 | -webkit-transform-origin: bottom center; 256 | width: 120px; 257 | } 258 | 259 | .orgchart.r2l .node .title { 260 | transform: rotate(-90deg) translate(-40px, -40px); 261 | -ms-transform: rotate(-90deg) translate(-40px, -40px); 262 | -moz-transform: rotate(-90deg) translate(-40px, -40px); 263 | -webkit-transform: rotate(-90deg) translate(-40px, -40px); 264 | transform-origin: bottom center; 265 | -ms-transform-origin: bottom center; 266 | -moz-transform-origin: bottom center; 267 | -webkit-transform-origin: bottom center; 268 | width: 120px; 269 | } 270 | 271 | .orgchart .node .title .symbol { 272 | float: left; 273 | margin-top: 4px; 274 | margin-left: 2px; 275 | } 276 | 277 | .orgchart .node .content { 278 | width: 100%; 279 | height: 20px; 280 | font-size: 11px; 281 | line-height: 18px; 282 | border: 1px solid rgba(217, 83, 79, 0.8); 283 | border-radius: 0 0 4px 4px; 284 | text-align: center; 285 | background-color: #fff; 286 | color: #333; 287 | overflow: hidden; 288 | text-overflow: ellipsis; 289 | white-space: nowrap; 290 | } 291 | 292 | .orgchart.b2t .node .content { 293 | transform: rotate(180deg); 294 | -ms-transform: rotate(180deg); 295 | -moz-transform: rotate(180deg); 296 | -webkit-transform: rotate(180deg); 297 | transform-origin: center top; 298 | -ms-transform-origin: center top; 299 | -moz-transform-origin: center top; 300 | -webkit-transform-origin: center top; 301 | } 302 | 303 | .orgchart.l2r .node .content { 304 | transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 305 | -ms-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 306 | -moz-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 307 | -webkit-transform: rotate(-90deg) translate(-40px, -40px) rotateY(180deg); 308 | transform-origin: top center; 309 | -ms-transform-origin: top center; 310 | -moz-transform-origin: top center; 311 | -webkit-transform-origin: top center; 312 | width: 120px; 313 | } 314 | 315 | .orgchart.r2l .node .content { 316 | transform: rotate(-90deg) translate(-40px, -40px); 317 | -ms-transform: rotate(-90deg) translate(-40px, -40px); 318 | -moz-transform: rotate(-90deg) translate(-40px, -40px); 319 | -webkit-transform: rotate(-90deg) translate(-40px, -40px); 320 | transform-origin: top center; 321 | -ms-transform-origin: top center; 322 | -moz-transform-origin: top center; 323 | -webkit-transform-origin: top center; 324 | width: 120px; 325 | } 326 | 327 | .orgchart .node .edge { 328 | font-size: 15px; 329 | position: absolute; 330 | color: rgba(68, 157, 68, 0.5); 331 | cursor: default; 332 | transition: .2s; 333 | -webkit-transition: .2s; 334 | } 335 | 336 | .orgchart.noncollapsable .node .edge { 337 | display: none; 338 | } 339 | 340 | .orgchart .edge:hover { 341 | color: #449d44; 342 | cursor: pointer; 343 | } 344 | 345 | .orgchart .node .verticalEdge { 346 | width: calc(100% - 10px); 347 | width: -webkit-calc(100% - 10px); 348 | width: -moz-calc(100% - 10px); 349 | left: 5px; 350 | } 351 | 352 | .orgchart .node .topEdge { 353 | top: -4px; 354 | } 355 | 356 | .orgchart .node .bottomEdge { 357 | bottom: -4px; 358 | } 359 | 360 | .orgchart .node .horizontalEdge { 361 | width: 15px; 362 | height: calc(100% - 10px); 363 | height: -webkit-calc(100% - 10px); 364 | height: -moz-calc(100% - 10px); 365 | top: 5px; 366 | } 367 | 368 | .orgchart .node .rightEdge { 369 | right: -4px; 370 | } 371 | 372 | .orgchart .node .leftEdge { 373 | left: -4px; 374 | } 375 | 376 | .orgchart .node .horizontalEdge::before { 377 | position: absolute; 378 | top: calc(50% - 7px); 379 | top: -webkit-calc(50% - 7px); 380 | top: -moz-calc(50% - 7px); 381 | } 382 | 383 | .orgchart .node .rightEdge::before { 384 | right: 3px; 385 | } 386 | 387 | .orgchart .node .leftEdge::before { 388 | left: 3px; 389 | } 390 | 391 | .orgchart .node .toggleBtn { 392 | position: absolute; 393 | left: 5px; 394 | bottom: -2px; 395 | color: rgba(68, 157, 68, 0.6); 396 | } 397 | 398 | .orgchart .node .toggleBtn:hover { 399 | color: rgba(68, 157, 68, 0.8); 400 | } 401 | 402 | .oc-export-btn { 403 | display: inline-block; 404 | position: absolute; 405 | right: 5px; 406 | top: 5px; 407 | padding: 6px 12px; 408 | margin-bottom: 0; 409 | font-size: 14px; 410 | font-weight: 400; 411 | line-height: 1.42857143; 412 | text-align: center; 413 | white-space: nowrap; 414 | vertical-align: middle; 415 | -ms-touch-action: manipulation; 416 | touch-action: manipulation; 417 | cursor: pointer; 418 | -webkit-user-select: none; 419 | -moz-user-select: none; 420 | -ms-user-select: none; 421 | user-select: none; 422 | color: #fff; 423 | background-color: #5cb85c; 424 | border: 1px solid transparent; 425 | border-color: #4cae4c; 426 | border-radius: 4px; 427 | } 428 | 429 | .oc-export-btn[disabled] { 430 | cursor: not-allowed; 431 | filter: alpha(opacity=30); 432 | -webkit-box-shadow: none; 433 | box-shadow: none; 434 | opacity: 0.3; 435 | } 436 | 437 | .oc-export-btn:hover,.oc-export-btn:focus,.oc-export-btn:active { 438 | background-color: #449d44; 439 | border-color: #347a34; 440 | } 441 | 442 | .orgchart~.mask { 443 | position: absolute; 444 | top: 0px; 445 | right: 0px; 446 | bottom: 0px; 447 | left: 0px; 448 | z-index: 999; 449 | text-align: center; 450 | background-color: rgba(0,0,0,0.3); 451 | } 452 | 453 | .orgchart~.mask .spinner { 454 | position: absolute; 455 | top: calc(50% - 54px); 456 | left: calc(50% - 54px); 457 | color: rgba(255,255,255,0.8); 458 | font-size: 108px; 459 | } 460 | 461 | .orgchart .node { 462 | transition: all 0.3s; 463 | webkit-transition: all 0.3s; 464 | opacity: 1; 465 | top: 0; 466 | left: 0; 467 | } 468 | 469 | .orgchart .slide-down { 470 | opacity: 0; 471 | top: 40px; 472 | } 473 | 474 | .orgchart.l2r .node.slide-down, .orgchart.r2l .node.slide-down { 475 | top: 130px; 476 | } 477 | 478 | .orgchart .slide-up { 479 | opacity: 0; 480 | top: -40px; 481 | } 482 | 483 | .orgchart.l2r .node.slide-up, .orgchart.r2l .node.slide-up { 484 | top: -130px; 485 | } 486 | 487 | .orgchart .slide-right { 488 | opacity: 0; 489 | left: 130px; 490 | } 491 | 492 | .orgchart.l2r .node.slide-right, .orgchart.r2l .node.slide-right { 493 | left: 40px; 494 | } 495 | 496 | .orgchart .slide-left { 497 | opacity: 0; 498 | left: -130px; 499 | } 500 | 501 | .orgchart.l2r .node.slide-left, .orgchart.r2l .node.slide-left { 502 | left: -40px; 503 | } -------------------------------------------------------------------------------- /src/main/resources/static/dist/js/jquery.orgchart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery OrgChart Plugin 3 | * https://github.com/dabeng/OrgChart 4 | * 5 | * Demos of jQuery OrgChart Plugin 6 | * http://dabeng.github.io/OrgChart/ 7 | * 8 | * Copyright 2016, dabeng 9 | * http://dabeng.github.io/ 10 | * 11 | * Licensed under the MIT license: 12 | * http://www.opensource.org/licenses/MIT 13 | */ 14 | 'use strict'; 15 | 16 | (function(factory) { 17 | if (typeof module === 'object' && typeof module.exports === 'object') { 18 | factory(require('jquery'), window, document); 19 | } else { 20 | factory(jQuery, window, document); 21 | } 22 | }(function($, window, document, undefined) { 23 | $.fn.orgchart = function(options) { 24 | var defaultOptions = { 25 | 'nodeTitle': 'name', 26 | 'nodeType': 'type', 27 | 'nodeId': 'Id', 28 | 'toggleSiblingsResp': false, 29 | 'depth': 999, 30 | 'chartClass': '', 31 | 'exportButton': false, 32 | 'exportFilename': 'OrgChart', 33 | 'exportFileextension': 'png', 34 | 'parentNodeSymbol': '', 35 | 'draggable': false, 36 | 'direction': 't2b', 37 | 'pan': false, 38 | 'zoom': false, 39 | 'zoominLimit': 7, 40 | 'zoomoutLimit': 0.5 41 | }; 42 | 43 | switch (options) { 44 | case 'buildHierarchy': 45 | return buildHierarchy.apply(this, Array.prototype.splice.call(arguments, 1)); 46 | case 'addChildren': 47 | return addChildren.apply(this, Array.prototype.splice.call(arguments, 1)); 48 | case 'addParent': 49 | return addParent.apply(this, Array.prototype.splice.call(arguments, 1)); 50 | case 'addSiblings': 51 | return addSiblings.apply(this, Array.prototype.splice.call(arguments, 1)); 52 | case 'removeNodes': 53 | return removeNodes.apply(this, Array.prototype.splice.call(arguments, 1)); 54 | case 'getHierarchy': 55 | return getHierarchy.apply(this, Array.prototype.splice.call(arguments, 1)); 56 | case 'hideParent': 57 | return hideParent.apply(this, Array.prototype.splice.call(arguments, 1)); 58 | case 'showParent': 59 | return showParent.apply(this, Array.prototype.splice.call(arguments, 1)); 60 | case 'hideChildren': 61 | return hideChildren.apply(this, Array.prototype.splice.call(arguments, 1)); 62 | case 'showChildren': 63 | return showChildren.apply(this, Array.prototype.splice.call(arguments, 1)); 64 | case 'hideSiblings': 65 | return hideSiblings.apply(this, Array.prototype.splice.call(arguments, 1)); 66 | case 'showSiblings': 67 | return showSiblings.apply(this, Array.prototype.splice.call(arguments, 1)); 68 | case 'getNodeState': 69 | return getNodeState.apply(this, Array.prototype.splice.call(arguments, 1)); 70 | case 'getRelatedNodes': 71 | return getRelatedNodes.apply(this, Array.prototype.splice.call(arguments, 1)); 72 | case 'setChartScale': 73 | return setChartScale.apply(this, Array.prototype.splice.call(arguments, 1)); 74 | default: // initiation time 75 | var opts = $.extend(defaultOptions, options); 76 | } 77 | 78 | // build the org-chart 79 | var $chartContainer = this; 80 | var data = opts.data; 81 | var $chart = $('
', { 82 | 'data': { 'options': opts }, 83 | 'class': 'orgchart' + (opts.chartClass !== '' ? ' ' + opts.chartClass : '') + (opts.direction !== 't2b' ? ' ' + opts.direction : ''), 84 | 'contextmenu': function(event) { 85 | if (!$(event.target).closest('.node').length) { 86 | $chart.find('.node.focused').removeClass('focused'); 87 | } 88 | } 89 | }); 90 | if ($.type(data) === 'object') { 91 | if (data instanceof $) { // ul datasource 92 | buildHierarchy($chart, buildJsonDS(data.children()), 0, opts); 93 | } else { // local json datasource 94 | buildHierarchy($chart, opts.ajaxURL ? data : attachRel(data, '00'), 0, opts); 95 | } 96 | } else { 97 | $.ajax({ 98 | 'url': data, 99 | 'dataType': 'json', 100 | 'beforeSend': function () { 101 | $chart.append(''); 102 | } 103 | }) 104 | .done(function(data, textStatus, jqXHR) { 105 | buildHierarchy($chart, opts.ajaxURL ? data : attachRel(data, '00'), 0, opts); 106 | }) 107 | .fail(function(jqXHR, textStatus, errorThrown) { 108 | console.log(errorThrown); 109 | }) 110 | .always(function() { 111 | $chart.children('.spinner').remove(); 112 | }); 113 | } 114 | $chartContainer.append($chart); 115 | 116 | // append the export button 117 | if (opts.exportButton && !$chartContainer.find('.oc-export-btn').length) { 118 | var $exportBtn = $('