bugfix250107.1
全站通知:

Widget:可视化推荐阅读编辑器

来自恋与深空WIKI_BWIKI_哔哩哔哩
跳到导航 跳到搜索


<script src="https://cdn.jsdelivr.net/npm/@logicflow/core/dist/index.min.js"></script> <link href="https://cdn.jsdelivr.net/npm/@logicflow/core/lib/style/index.min.css" rel="stylesheet">

<script src="https://cdn.jsdelivr.net/npm/@logicflow/extension/dist/index.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/style/index.min.css" />

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

<body>

</body>

<script>

   const { HtmlResize, dynamicGroup, DndPanel, Menu, Control, MiniMap, DynamicGroup, SelectionSelect } = Extension;
   const { LogicFlow, h, BaseText } = Core;
   // 暂定五种节点:主线、世界深处、约会剧情、活动剧情、手机信息
   // 定义主线节点
   class CommonModel extends HtmlResize.model {
       initNodeData(data) {
           super.initNodeData(data);
           this.width = 200;
           this.height = 50;
       }
       getTextStyle() {
           const style = super.getTextStyle();
           style.color = this.properties.textColor || '#aa7952';
           style.fontSize = 15
           return style;
       }
   }
   class MainLineNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           // node_md.properties 获取节点属性 
           let el = document.createElement("div");
           el.className = "main-line-node node-div";
           el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const MainLineBox = {
       type: "main_line_node",
       view: MainLineNode,
       model: CommonModel,
   };
   // 定义世界深处节点
   class WorldDeepNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           let el = document.createElement("div");
           el.className = "world-deep-node node-div";
           el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const WorldDeepBox = {
       type: "world_deep_node",
       view: WorldDeepNode,
       model: CommonModel,
   };
   // 定义约会剧情节点
   class DatePlotNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           let el = document.createElement("div");
           el.className = "date-plot-node node-div";
           el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const DatePlotBox = {
       type: "date_plot_node",
       view: DatePlotNode,
       model: CommonModel,
   };
   // 定义活动剧情节点
   class ActivityPlotNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           let el = document.createElement("div");
           el.className = "activity-plot-node node-div";
           el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const ActivityPlotBox = {
       type: "activity_plot_node",
       view: ActivityPlotNode,
       model: CommonModel,
   };
   // 定义手机信息节点
   class PhoneInfoNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           let el = document.createElement("div");
           el.className = "phone-info-node node-div";
           el.id = node_md.id; // html 节点绑定节点唯一ID;即可通过id 获取对应dom元素 并进行相关业务操作
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const PhoneInfoBox = {
       type: "phone_info_node",
       view: PhoneInfoNode,
       model: CommonModel,
   };
   // 定义分组节点
   class GroupModel extends dynamicGroup.model {
       initNodeData(data) {
           super.initNodeData(data);
           // 设置默认样式
           this.properties = {
               ...this.properties,
               collapsible: false,
               width: 300,
               height: 450
           }
           this.collapsible = false;
       }
       getNodeStyle() {
           const style = super.getNodeStyle();
           style.fill = "transparent";
           style.strokeWidth = 0;
           return style;
       }
       // 处理高亮样式
       getAddableOutlineStyle() {
           const style = super.getAddableOutlineStyle();
           style.stroke = "#FF9800";
           style.strokeDasharray = "5 5";
           return style;
       }
       // 节点内文本样式
       // getTextStyle() {
       //     const style = super.getTextStyle();
       //     style.fontSize = 20;
       //     style.color = this.properties.textColor || '#aa7952';  // 修改文字颜色
       //     return style;
       // }
   }
   class GroupView extends dynamicGroup.view {
       // 由于分组节点没有内置自定义html方法,因此重写getShape方法,加入自定义div元素
       getShape() {
           const { model } = this.props
           const { width, height, x, y, radius } = model
           const rectSvg = super.getShape();
           return h('g', {}, [
               rectSvg,
               h('g', {}, [
                   // 写入自定义内容-用于设置分组节点样式、背景图片
                   h('foreignObject', {
                       x: x - width / 2,
                       y: y - height / 2,
                       width: width,
                       height: height,
                       radius: radius,
                   }, [
                       // 注意:foreignObject内的HTML元素需要指定xhtml命名空间
                       h('div', {
                           xmlns: 'http://www.w3.org/2000/xhtml',
                           class: 'group-node'
                       })
                   ])
               ])
           ]);
       }
       // getText() {
       //     const { model, graphModel } = this.props
       //     const { editConfigModel } = graphModel
       //     // 当 节点文本模式非 TEXT 时,不显示文本
       //     if (editConfigModel.nodeTextMode !== 'text') return null
       //     // 文本被编辑的时候,显示编辑框,不显示文本。
       //     if (model.state === 'TEXT_EDIT') return null
       //     if (model.text) {
       //         let draggable = false
       //         if (editConfigModel.nodeTextDraggable && model.text.draggable) {
       //             draggable = true
       //         }
       //         return h('BaseText', {
       //             editable: editConfigModel.nodeTextEdit && (model.text.editable ?? true),
       //             model: model,
       //             graphModel: graphModel,
       //             draggable: draggable,
       //         })
       //         // (
       //         //     <
       //         //         editable={
       //         //             editConfigModel.nodeTextEdit && (model.text.editable ?? true)
       //         //         }
       //         //         model={model}
       //         //         graphModel={graphModel}
       //         //         draggable={draggable}
       //         //     />
       //         // )
       //     }
       //     return null
       // }
   }
   const GroupBox = {
       type: "group_node",
       model: GroupModel,
       view: GroupView,
   }
   // 定义主标题
   class MainTitleModel extends HtmlResize.model {
       initNodeData(data) {
           super.initNodeData(data);
           this.width = 200;
           this.height = 50;
       }
       getTextStyle() {
           const style = super.getTextStyle();
           style.fontSize = 24;
           style.color = this.properties.textColor || '#aa7952';
           return style;
       }
   }
   class MainTitleNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           let el = document.createElement("div");
           el.className = "main-title-node title-node";
           el.id = node_md.id;
           el.setAttribute("data-text", node_md.text.value);
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const MainTitleBox = {
       type: "main_title",
       model: MainTitleModel,
       view: MainTitleNode,
   }
   // 定义副标题
   class SubTitleModel extends HtmlResize.model {
       initNodeData(data) {
           super.initNodeData(data);
           this.width = 200;
           this.height = 50;
       }
       getTextStyle() {
           const style = super.getTextStyle();
           style.fontSize = 20;
           style.color = this.properties.textColor || '#aa7952';
           // style.overflowMode = 'autoWrap'
           return style;
       }
   }
   class SubTitleNode extends HtmlResize.view {
       setHtml(rootEl) {
           let node_md = this.props.model;
           let el = document.createElement("div");
           el.className = "sub-title-node title-node";
           el.id = node_md.id;
           el.setAttribute("data-text", node_md.text.value);
           el.innerHTML = "";
           // 需要先把之前渲染的子节点清除掉。
           rootEl.innerHTML = "";
           rootEl.appendChild(el);
       }
   }
   const SubTitleBox = {
       type: "sub_title",
       model: SubTitleModel,
       view: SubTitleNode,
   }
   // 定义拓展阅读节点
   class ExtendedReadModel extends dynamicGroup.model {
       initNodeData(data) {
           super.initNodeData(data);
           // 设置默认样式
           this.properties = {
               ...this.properties,
               collapsible: false,
               width: 250,
               height: 150
           }
           this.collapsible = false;
       }
       getNodeStyle() {
           const style = super.getNodeStyle();
           style.fill = "transparent";
           style.strokeWidth = 0;
           return style;
       }
       // 处理高亮样式
       getAddableOutlineStyle() {
           const style = super.getAddableOutlineStyle();
           style.stroke = "#FF9800";
           style.strokeDasharray = "5 5";
           return style;
       }
   }
   class ExtendedReadNode extends dynamicGroup.view {
       // 由于分组节点没有内置自定义html方法,因此重写getShape方法,加入自定义div元素
       getShape() {
           const { model } = this.props
           const { width, height, x, y, radius } = model
           const rectSvg = super.getShape();
           return h('g', {}, [
               rectSvg,
               h('g', {}, [
                   // 写入自定义内容-用于设置分组节点样式、背景图片
                   h('foreignObject', {
                       x: x - width / 2,
                       y: y - height / 2,
                       width: width,
                       height: height,
                       radius: radius,
                   }, [
                       // 注意:foreignObject内的HTML元素需要指定xhtml命名空间
                       h('div', {
                           xmlns: 'http://www.w3.org/2000/xhtml',
                           class: 'extended-read-node'
                       })
                   ])
               ])
           ]);
       }
   }
   const ExtendedReadBox = {
       type: "extended_read_node",
       view: ExtendedReadNode,
       model: ExtendedReadModel,
   };


   // 定义hover面板
   class HoverNode {
       static pluginName = 'hoverNode'
       constructor({ lf, LogicFlow }) {
           this.lf = lf;
           this.allowHover = false;  // 默认关闭
       }
       render(lf, container) {
           this.container = container;
           lf.on("node:mouseenter", ({ data }) => {
               if (!this.allowHover) return;
               if (data.properties.recommond || data.properties.pageUrl) {
                   // 创建一个div节点,将recommond内容渲染为html
                   let panelDiv = document.createElement("div");
                   panelDiv.id = "node-page-panel";
                   // 计算悬浮框位置
                   panelDiv.style.left = this.lf.getTransform().SCALE_X * (data.x + data.properties.width / 2) + this.lf.getTransform().TRANSLATE_X + "px";
                   panelDiv.style.top = this.lf.getTransform().SCALE_Y * (data.y - data.properties.height / 2) + this.lf.getTransform().TRANSLATE_Y + "px";
                   panelDiv.innerHTML = `
${data.properties.recommond}
                       <button class="node-button" ${data.properties.pageUrl ?  : 'disabled'} 
                           style="${data.properties.pageUrl ? 'cursor: pointer; color: #aa7952;' : 'color: #aa79524d;'}"
                           onclick="window.open('${data.properties.pageUrl}')">跳转</button>
                   `;
                   this.container.appendChild(panelDiv);
                   panelDiv.addEventListener("mouseleave", () => {
                       this.container.removeChild(panelDiv);
                   });
                   let panelButton = $('.node-button')[0];
                   panelButton.addEventListener("mousedown", () => {
                       panelButton.style.backgroundImage = "url(https://patchwiki.biligame.com/images/lysk/b/ba/8wn6pkomoljy6s809xc50g5gkt6aq6l.png)";
                   });
                   panelButton.addEventListener("mouseup", () => {
                       panelButton.style.backgroundImage = "url(https://patchwiki.biligame.com/images/lysk/7/7f/8cfam9dgpmzkz2om9j2bjyyqeu9q9sf.png)";
                   });
                   panelButton.addEventListener("mouseleave", () => {
                       panelButton.style.backgroundImage = "url(https://patchwiki.biligame.com/images/lysk/7/7f/8cfam9dgpmzkz2om9j2bjyyqeu9q9sf.png)";
                   });
               }
           });
           lf.on("node:mouseleave", ({ data, e }) => {
               if (!this.allowHover) return;
               if (data.type == 'group_node') return;
               setTimeout(() => {
                   let mouseX = e.offsetX;
                   let mouseY = e.offsetY;
                   let panelDiv = document.querySelector("#node-page-panel");
                   if (panelDiv) {
                       // 查看鼠标是否在#node-page-panel节点上
                       if (mouseX >= panelDiv.offsetLeft && mouseX <= panelDiv.offsetLeft + panelDiv.offsetWidth &&
                           mouseY >= panelDiv.offsetTop && mouseY <= panelDiv.offsetTop + panelDiv.offsetHeight) {
                           return;
                       }
                       else {
                           this.container.removeChild(panelDiv);
                       }
                   }
               }, 100);
           });
       }
       // 开启hover面板
       openHoverPanel() {
           this.allowHover = true;
       }
       // 关闭hover面板
       closeHoverPanel() {
           this.allowHover = false;
       }
   }
   // TODO 虚线样式
   // class SequenceModel extends PolylineEdgeModel {
   //     // 设置边样式
   //     getEdgeStyle() {
   //         const style = super.getEdgeStyle();
   //         const { properties } = this;
   //         if (properties.isstrokeDashed) {
   //             style.strokeDasharray = '4, 4';
   //         }
   //         return style;
   //     }
   // }
   // export default {
   //     type: 'sequence',
   //     view: PolylineEdge,
   //     model: SequenceModel,
   // };
   // TODO 修改节点文本颜色
   // graphModel.getNodeModelById(currentNodeId).setProperties({ textColor: '#fff' });


   const lf = new LogicFlow({
       container: document.querySelector("#container"),
       // grid: true,
       // edgeType: 'bezier',
       allowResize: true,
       disabledTools: ['multipleSelect'],
       // multipleSelectKey:"shift",
       keyboard: {
           enabled: true,
       },
       background: {
           backgroundImage: "url(https://patchwiki.biligame.com/images/lysk/4/40/0ibuxl73pgr095hvneel8bf1kwxp6r7.jpg)",
           backgroundRepeat: "repeat",
       },
       idGenerator: (type) => {  //重写全局id
           let edgeTypes = ['line', 'polyline', 'bezier'];
           // 重写node节点id
           if (!edgeTypes.includes(type)) {
               let id_num = graphModel.nodes.length + 1;
               for (let i = 0; i < graphModel.nodes.length; i++) {
                   if (id_num == graphModel.nodes[i].id.split('_')[1]) {
                       id_num++;
                       i--;
                   }
               }
               return 'node_' + id_num;
           }
       },
       plugins: [DndPanel, Menu, Control, DynamicGroup, SelectionSelect, HoverNode],  //MiniMap
       pluginsOptions: {
           miniMap: {
               isShowCloseIcon: true,
           },
       },
   });
   //注册自定义节点
   lf.batchRegister([MainLineBox, WorldDeepBox, DatePlotBox, ActivityPlotBox, PhoneInfoBox, GroupBox, MainTitleBox, SubTitleBox, ExtendedReadBox]);//
   lf.extension.dndPanel.setPatternItems([
       {
           type: 'main_line_node',
           text: '主线节点',
           label: '主线节点',
           properties: {},
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAP1JREFUaEPtmUEOgjAQRWfibfQS3oMmamLwGOIxMC5ctPfAQ+hxGANBorhg8RPaJp8tzPTPe10xKpk/mnl+4QCxDdJAMgacO27NVjtV28cONXP+RaRtvL813XfjFXKurETknHj4Pp6Z3kOoD9MBbHi5CaF+pThIUZzWqvbssnl/7eF/G+gH+LxIcYAuk3PlT04OsLQpGlia+PQ8GqABkACvEAgQLqcBGCHYgAZAgHA5DcAIwQY0AAKEy2kARgg2oAEQIFxOAzBCsAENgADhchqAEYINaAAECJfPGjDTbH+v577gyGbFVIm0j78VE3w5IzXgmjUS+PFYGqABkMAbG/IqQCYDb4cAAAAASUVORK5CYII='
       },
       {
           type: 'world_deep_node',
           text: '世界深处',
           label: '世界深处',
           properties: {},
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQZJREFUaEPtmU0OgjAQhadlixfRS7jy5w5G5CTiSdDEOyg7PIRcBLZSUxMwYqKLl9A2eS6Rmb75vq4YJYH/VOD5hQO4NkgD3hiIV/lcmSgRMTvXoX6db0x7EC1lc01L+15/heJFniml9z6Hf2dTx7rYph8DTJYnYx8YHc2ay6bycZB4fZ6q9nG32eoiecHvDXQDdH/4OIDNNMzJAcY2RQNjEx+eRwM0ABLgFQIBwuU0ACMEG9AACBAupwEYIdiABkCAcDkNwAjBBjQAAoTLaQBGCDagARAgXE4DMEKwAQ2AAOHyvwaC/bwe/IIjnBWTZKLb29eKCb6cjhpwzeoIfH8sDdAASOAJUZEoQF1l6DYAAAAASUVORK5CYII='
       },
       {
           type: 'date_plot_node',
           text: '倾心之约',
           label: '倾心之约',
           properties: {},
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQhJREFUaEPtmTEKwkAQRWciYqMX0VZiK8Z7qCcx3sSDGIhlxFYvEhsRsrJCIkbQ4kN2F75lzMz+eW+rjErgPw08v3AA1wZpwBsD5XI6V9NbiZi161C/zjdGdhJV+ehwzu17zRUqF3GqKlufw7+z6X6YFZuPAW5JbOyDvuhkkBVXHwe5J7PxQ8zFZhtmpxf8xkA9QP2HjwPYTO2cHKBrUzTQNfH2eTRAAyABXiEQIFxOAzBCsAENgADhchqAEYINaAAECJfTAIwQbEADIEC4nAZghGADGgABwuU0ACMEG9AACBAu/2sg2M/rwS84QlkxqZG0iqrj14oJvpyOGnDN6gh8cywN0ABI4Am8RCZADpxRJQAAAABJRU5ErkJggg=='
       },
       {
           type: 'activity_plot_node',
           text: '活动剧情',
           label: '活动剧情',
           properties: {},
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQZJREFUaEPtmUEOgkAMRds5jl5BjEvlEuodYC0uDR7CU6BL0DPocWYUEkjERBc/YQbzWcK0077XFVUZ+aMjr1/YgG+DNBCMgfy2XKiatXOy8V3Ut/utlb0xtkyjS1mf60boUMWZMbILufi2NlU5JbNi+9ZAfotd88K5aTI/P0Js5HhdTZzqva4tjYoGfmegbaD9EGIDdU39OtnA0KZoYGji/ftogAZAAhwhECAcTgMwQjABDYAA4XAagBGCCWgABAiH0wCMEExAAyBAOJwGYIRgAhoAAcLhNAAjBBPQAAgQDv9pYLS/1/9gwTGOFdNrBjMRW32smODh9JSAa1ZP4LtraYAGQAJPbn4mQI7cPW8AAAAASUVORK5CYII='
       },
       {
           type: 'phone_info_node',
           text: '手机信息',
           label: '手机信息',
           properties: {},
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAQhJREFUaEPtmdsJwkAQRWdiK6KiTVhCrMAHsQ5jHRGxg6QELUJRsRV3JUIWjKAfF7K7cP2Mmdk75+xXRiXyn0aeXziAb4M0EIyBYlRO1ehcVBa+Q/0631q7lUSO6+vsWL/nrlAxKHNV3YQc3mWzcsju6fJjgN2wsvWD5CmT1SO9hDjIvl+NTU/Odbbslr7hOwPNAM0fIQ5QZ2rn5ABdm6KBrom3z6MBGgAJ8AqBAOFyGoARgg1oAAQIl9MAjBBsQAMgQLicBmCEYAMaAAHC5TQAIwQb0AAIEC6nARgh2IAGQIBw+V8D0X5ej37BEcuKSa3mJjGnrxUTfDk9NeCa1RN4dywN0ABI4AXYnCZABsa7xAAAAABJRU5ErkJggg=='
       },
       {
           type: 'group_node',
           label: '分组',
           text: ,
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhRJREFUWEftlj+L1EAYxp8n6yoRPRDxrlG4YnFhk9nGyu4stRLhuFK/gKUifgBBEAU9bb3af5wIIoiCFtrvzBUWguB19wXuCvOYCcmx7q3ZjYSsxQ4M82Z4553fPO87SYgKLY7jKwBeVlhS5vpZ0j1WCdbr9a4HQfCwyhrvKwnkwa0kbVUC6Ha7x9vt9jkAh4suad8m2ZY0MSbJRQA3AByd6Fz1tNP4G2MuSdoAcKoUwBhzEcATSQGAgGRQ2P4ZgF+fzSdJko0jNot5SQ9ardb9wWCwHcfxCoBnEwGiKNogeXWaU436+Jz73I+07865ztQA3lHSNZKHAPjuc5yN+VzpM4cqL5V82cM455gDvABwspEaMMasSnrsJS8ASL6SdCIDMMacT6v5zr9I/bc1JG9ba7/m8T3AelpPiwWApNckF7wcHwFcqHPzoVifnHMruQJ/AAB4A+CYBzhQKXXC+BOPAyD5VlKYAaSF9iMIgpvW2ud1bD6a83EAkt6RPNIYAIB1Sfs1AOC9v1mzBPiQvcSaSkF65x9JWhq6BU9JLs8B5grMFZiJAp1OZyEMwy+SdmYCMPy6nwM0okC/3z+dJMlPANvOuTONp6DsE9+IAv8/AIAd/8daQvoLwK7vkvZIjrN3Se7lPt5eAxD6z2+pAlEUOZJRHb9iY2JY51y/FMAYczklvSvpbJ0QJL+latyy1m6Wxf0N485wXg1uXQsAAAAASUVORK5CYII=',
       },
       {
           type: 'main_title',
           label: '主标题',
           text: '主标题',
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAP5JREFUWEftljFqw0AQRXcO4DIEgQ/jtOnlPpdIZbR/FlZF7hDcJwdIGV/AnXGVLo1BN1iBJggkiIRkFoMkDKNOMMM83kh/l8zCDy083yhAxwCAt3olAF5jVuO9T0IIOxEpnHOI6enXdACstVIXMHPUagBsROTbGHNg5icFUANqYMyA9/6xLMsVgJ/Zf8Msy1Ii+iCiLwDPswK0w9uhQ/kyWRD1h48F3CDADV/zhZmTtg/AWkR+m/dPY0w6K0BznpxE5MzM22sRP9kK/ltUADWgBtTAXRnYN5n9EnMe5Hn+EEJ4r6rq6JzjsR5r7Z6IiqHrftT1Owbm1hoFWNzAHxywbTDOHITTAAAAAElFTkSuQmCC',
       },
       {
           type: 'sub_title',
           label: '副标题',
           text: '副标题',
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACA0lEQVR4AeyUP0vDQBjG026Ck4sg7m4ubg7STgVBcPHPIlqkOpTi5J/BtpcuFYUiKoJd7FoUKrh00g6Oboqji/0C/QC2/i4kJUmvmpZEpbTc0+e9uzf3PHlzd2Htj39DA/+rAtls9loIcex1W/Sar1rXXYGNVqu1q0rsMtZrfscybgMdCUEPDA0MKzC4FSgUCiOZTCbFXVEBB+l0ekZ1ogKpAJfZbKPRqIdCoTNEF0E+HA4/Y+SK2NF8N4D4KJfZDSpjoIaJbbgEZNuiKkkZWFAawGnLC6xF7NxsNlfoTyBc0XU9iqGirutxxgpAY3xTsgWlAWuyH0YgZj53Z7JBjJ8bgaZNmWyQ0gCOQ15grOD6o/xvDF2CB2BvEdnByItkC0oD1mQ/nMvlBOaTlL5ufx5jS7LPJ6pKtuC7AWthO2OmSH8evHMabuF2C9wA4ke8fcJUXKf/asYGBWoAsQji+1IJXuXTPMnYjsAMcN4Foo+m2AV7o2zGDgrEAOJr7PasVMIEL66nZKyC7wYo+zRCJ0BD/JA3FzLuBt8NIDTH248jfg9/Uo0dkOBmXYZjGJwkp918N4BwVK6O+AKch0+BPIZluMr8B+Pt5jZQIskoXzvj+6AjHwF509V4TAnmHZ/EYYDdEhdC7PGwp6bKl9+c8Wg3MK/bF3cYsE/8Vjz4Bn6q5BcAAAD//3mzGmkAAAAGSURBVAMA7QHjQec9SZUAAAAASUVORK5CYII=',
       },
       {
           type: 'extended_read_node',
           label: '拓展阅读',
           text: ,
           icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhRJREFUWEftlj+L1EAYxp8n6yoRPRDxrlG4YnFhk9nGyu4stRLhuFK/gKUifgBBEAU9bb3af5wIIoiCFtrvzBUWguB19wXuCvOYCcmx7q3ZjYSsxQ4M82Z4553fPO87SYgKLY7jKwBeVlhS5vpZ0j1WCdbr9a4HQfCwyhrvKwnkwa0kbVUC6Ha7x9vt9jkAh4suad8m2ZY0MSbJRQA3AByd6Fz1tNP4G2MuSdoAcKoUwBhzEcATSQGAgGRQ2P4ZgF+fzSdJko0jNot5SQ9ardb9wWCwHcfxCoBnEwGiKNogeXWaU436+Jz73I+07865ztQA3lHSNZKHAPjuc5yN+VzpM4cqL5V82cM455gDvABwspEaMMasSnrsJS8ASL6SdCIDMMacT6v5zr9I/bc1JG9ba7/m8T3AelpPiwWApNckF7wcHwFcqHPzoVifnHMruQJ/AAB4A+CYBzhQKXXC+BOPAyD5VlKYAaSF9iMIgpvW2ud1bD6a83EAkt6RPNIYAIB1Sfs1AOC9v1mzBPiQvcSaSkF65x9JWhq6BU9JLs8B5grMFZiJAp1OZyEMwy+SdmYCMPy6nwM0okC/3z+dJMlPANvOuTONp6DsE9+IAv8/AIAd/8daQvoLwK7vkvZIjrN3Se7lPt5eAxD6z2+pAlEUOZJRHb9iY2JY51y/FMAYczklvSvpbJ0QJL+latyy1m6Wxf0N485wXg1uXQsAAAAASUVORK5CYII=',
       },
   ])
   lf.extension.menu.setMenuConfig({
       nodeMenu: [
           {
               text: "删除",
               callback(node) {
                   lf.deleteNode(node.id);
               },
           },
       ], // 覆盖默认的节点右键菜单
   });
   lf.extension.control.removeItem('zoom-out');
   lf.extension.control.removeItem('zoom-in');
   lf.extension.control.removeItem('reset');
   lf.extension.control.addItem({   // 小地图导航
       key: 'mini-map',
       iconClass: "custom-minimap",
       title: "",
       text: "导航",
       onClick: (lf, ev) => {
           lf.extension.miniMap.show();
           lf.extension.miniMap.updatePosition('right-top');
       },
   });
   lf.extension.control.addItem({   //导入json
       iconClass: 'input-json',
       title: "",
       text: "导入json",
       onClick: (lf, ev) => {
           $('#input-json')[0].style.display = ;
           $('#model-json-input')[0].value = ;
       },
   });
   lf.extension.control.addItem({   //打印json
       iconClass: 'log-json',
       title: "",
       text: "导出json",
       onClick: (lf, ev) => {
           const modelData = graphModel.modelToGraphData(); //图形->数据
           $('#show-json')[0].style.display = ;
           $('.json-content')[0].innerText = JSON.stringify(modelData);
       },
   });
   lf.extension.control.addItem({   //保存
       iconClass: 'save-json',
       title: "",
       text: "保存",
       onClick: (lf, ev) => {
           const request = indexedDB.open('readDatabase');
           request.onerror = function (event) {
               console.error('Database error: ', event.target.errorCode);
           };
           request.onsuccess = function (event) {
               const db = event.target.result;
               const newData = { id: 'readData', data: JSON.stringify(graphModel.modelToGraphData()) };
               addData(db, newData);
           };
       },
   });
   lf.extension.control.addItem({   // 开启框选
       iconClass: "selection-open",
       title: "",
       text: "开启框选",
       onClick: (lf, ev) => {
           // 开启框选
           graphModel.editConfigModel.updateEditConfig({
               stopMoveGraph: true,
           })
           lf.openSelectionSelect();
           showCustomAlert('框选已开启!');
       },
   });
   lf.extension.control.addItem({   // 关闭框选
       iconClass: "selection-close",
       title: "",
       text: "关闭框选",
       onClick: (lf, ev) => {
           // 开启框选
           graphModel.editConfigModel.updateEditConfig({
               stopMoveGraph: false,
           })
           lf.closeSelectionSelect();
           showCustomAlert('框选已关闭!');
       },
   });
   lf.extension.control.addItem({  // 开启悬浮框
       iconClass: "hoverpanel-open",
       title: "",
       text: "开启悬浮框",
       onClick: (lf, ev) => {
           lf.extension.hoverNode.openHoverPanel();
           showCustomAlert('节点悬浮框已开启!');
       },
   });
   lf.extension.control.addItem({  // 关闭悬浮框
       iconClass: "hoverpanel-close",
       title: "",
       text: "关闭悬浮框",
       onClick: (lf, ev) => {
           lf.extension.hoverNode.closeHoverPanel();
           showCustomAlert('节点悬浮框已关闭!');
       },
   });


   // 消息提示框
   function showCustomAlert(message) {
       $('#buttons-tip')[0].innerText = message;
       $('#buttons-tip')[0].style.display = ;
       setTimeout(() => {
           $('#buttons-tip')[0].style.display = 'none';
       }, 1000);
   }
   // 复制json到剪贴板
   $('.copy-json')[0].addEventListener('click', (event) => {
       async function copyToClipboard(text) {
           try {
               await navigator.clipboard.writeText(text);
               alert('已复制到剪贴板!');
           } catch (err) {
               console.error('Failed to copy text: ', err);
           }
       }
       copyToClipboard($('.json-content')[0].innerText);
   })
   // 关闭json弹窗
   $('.close-json')[0].addEventListener('click', (event) => {
       $('#show-json')[0].style.display = 'none';
   })
   // 确认导入
   $('.confirm-input')[0].addEventListener('click', (event) => {
       graphData = JSON.parse($('#model-json-input')[0].value);
       lf.render(graphData);
       $('#input-json')[0].style.display = 'none';
       console.log('流程数据:', graphData)
   })
   $('.cancle-input')[0].addEventListener('click', (event) => {
       $('#input-json')[0].style.display = 'none';
       $('#model-json-input')[0].value = ;
   })
   // 输入节点属性json
   $('#node-recommond-input')[0].addEventListener('input', (event) => {
       graphModel.getNodeModelById(currentNodeId).setProperties({ recommond: event.target.value });
   })
   $('#node-url-input')[0].addEventListener('input', (event) => {
       graphModel.getNodeModelById(currentNodeId).setProperties({ pageUrl: event.target.value });
   })
   // 关闭属性栏
   $('.close-panel')[0].addEventListener('click', (event) => {
       $('.right-panel')[0].style.display = 'none';
   })
   let graphData, graphModel;
   const types = ['main_line_node', 'world_deep_node', 'date_plot_node', 'activity_plot_node', 'phone_info_node']
   // 从indexedDB中读取数据
   const loadDatafromDB = () => {
       const request = indexedDB.open('readDatabase');
       request.onerror = function (event) {
           console.error('Database error: ', event.target.errorCode);
       };
       request.onsuccess = function (event) {
           const db = request.result;
           const transaction = db.transaction(['readDataStore'], 'readonly');
           const store = transaction.objectStore('readDataStore');
           const result = store.get('readData');
           result.onsuccess = function (event) {
               const data = result.result;
               if (data) {
                   graphData = JSON.parse(data.data);
               } else {
                   graphData = {};
               }
               console.log('流程数据:', graphData);
               lf.render(graphData);  //渲染
               graphModel = lf.graphModel;
           };
           result.onerror = function (event) {
               console.error('Error retrieving data.', event.target.errorCode);
           };
       }
       request.onupgradeneeded = function (event) {
           let db = event.target.result;
           if (!db.objectStoreNames.contains("readDataStore")) {
               db.createObjectStore("readDataStore", { keyPath: "id" });
           }
       };
   }
   loadDatafromDB();
   // 向indexedDB中写入数据readData
   function addData(db, data) {
       const transaction = db.transaction(['readDataStore'], 'readwrite');
       const store = transaction.objectStore('readDataStore');
       const request = store.put(data);
       request.onsuccess = function (event) {
           alert('数据缓存成功!');
       };
       request.onerror = function (event) {
           console.error('Error adding data.', event.target.errorCode);
       };
   }
   // 节点点击事件
   var currentNodeId = ;
   lf.on("node:click", (node) => {
       console.log('节点数据:', node.data);
       currentNodeId = node.data.id;
       // 显示属性栏
       if (types.includes(node.data.type)) {
           $('.right-panel')[0].style.display = ;
           const currentNodeModel = graphModel.getNodeModelById(currentNodeId);
           $('#current-node')[0].innerText = `${currentNodeModel.text.value}(${currentNodeModel.id})`;
           $('#node-recommond-input')[0].value = currentNodeModel.properties.recommond || ;
           $('#node-url-input')[0].value = currentNodeModel.properties.pageUrl || ;
       }
   });
   // 节点添加事件
   lf.on("node:dnd-add", ({ data }) => {
       // 修改分组节点的文本位置
       setTimeout(() => {
           if (data.type == 'main_title') {
               let text = graphModel.getNodeModelById(data.id).text;
               text.x = data.x - data.properties.width / 2 + 20 + text.value.length * 17;
               text.y = data.y - data.properties.height / 2 + 20;
           }
           if (data.type == 'sub_title') {
               let text = graphModel.getNodeModelById(data.id).text;
               text.x = data.x - data.properties.width / 2 + 12 + text.value.length * 15;
               text.y = data.y - data.properties.height / 2 + 17;
           }
           changeLetterSpacing();
       }, 100)
   })
   // 更改文本
   lf.on("text:update", ({ data }) => {
       if (data.type == 'main_title') {
           // 修改文本节点位置并更新文本内容
           let oldText = $(`#${data.id}`)[0].getAttribute('data-text');
           lf.getNodeModelById(data.id).moveText((data.text.length - oldText.length) * 17, 0)
           $(`#${data.id}`)[0].setAttribute('data-text', data.text);
       }
       if (data.type == 'sub_title') {
           // 修改文本节点位置并更新文本内容
           let oldText = $(`#${data.id}`)[0].getAttribute('data-text');
           lf.getNodeModelById(data.id).moveText((data.text.length - oldText.length) * 15, 0)
           $(`#${data.id}`)[0].setAttribute('data-text', data.text);
       }
       setTimeout(() => {
           changeLetterSpacing();
       }, 100)
   })
   const changeLetterSpacing = () => {
       let titleNodes = $('.title-node');
       for (let i = 0; i < titleNodes.length; i++) {
           titleNodes[i].parentElement.parentElement.nextSibling.childNodes[0].setAttribute('letter-spacing', 10)
       }
   }
   // 节点缩放事件
   lf.on("node:resize", (e) => {
       // 重写分组节点的文本位置
       if (e.data.type == 'main_title') {
           let height = e.data.properties.height;
           let width = e.data.properties.width;
           let text = e.model.text
           text.x = e.data.x - width / 2 + 20 + text.value.length * 17;
           text.y = e.data.y - height / 2 + 20;
       }
       if (e.data.type == 'sub_title') {
           let height = e.data.properties.height;
           let width = e.data.properties.width;
           let text = e.model.text
           text.x = e.data.x - width / 2 + 12 + text.value.length * 15;
           text.y = e.data.y - height / 2 + 17;
       }
   })
   lf.on('node:mouseenter', ({ data, e }) => {
       if (types.includes(data.type)) {
           $(`#${data.id}`)[0].style.backgroundImage = 'url(https://patchwiki.biligame.com/images/lysk/1/1b/sxyq7pmcdcwe6uazcy7uqk89losay2s.png)';
           $(`#${data.id}`)[0].style.backgroundSize = '100% 110%';
       }
   })
   lf.on('node:mouseleave', ({ data, e }) => {
       if (types.includes(data.type)) {
           $(`#${data.id}`)[0].style.backgroundImage = 'url(https://patchwiki.biligame.com/images/lysk/5/51/241yaplcz3ortw7m1n5o2fn37sotr7o.png)';
           $(`#${data.id}`)[0].style.backgroundSize = '100% 90%';
       }
   })

</script>

<style>

   /* .main-line-node,
   .world-deep-node,
   .date-plot-node,
   .activity-plot-node,
   .phone-info-node  */
   .node-div {
       width: 100%;
       height: 100%;
       background-image: url(https://patchwiki.biligame.com/images/lysk/5/51/241yaplcz3ortw7m1n5o2fn37sotr7o.png);
       background-size: 100% 90%;
       background-repeat: no-repeat;
       background-position: center;
       cursor: pointer;
       box-sizing: border-box;
   }
   .main-title-node::after {
       content: attr(data-text);
       position: absolute;
       line-height: 100%;
       left: 20px;
       top: 55px;
       font-size: 24px;
       letter-spacing: 10px;
       transform: scaleY(-1);
       transform-origin: top;
       background: linear-gradient(to bottom, transparent 30%, #aa79525c);
       -webkit-background-clip: text;
       background-clip: text;
       -webkit-text-fill-color: transparent;
   }
   .sub-title-node {
       background-image: url(https://patchwiki.biligame.com/images/lysk/c/c1/c2xu5ggcepuj340avoyfrf782dxudm7.png);
       background-size: contain;
       background-repeat: no-repeat;
       width: 100%;
       height: 100%;
   }
   .sub-title-node::after {
       content: attr(data-text);
       position: absolute;
       line-height: 100%;
       left: 12px;
       top: 50px;
       font-size: 20px;
       letter-spacing: 10px;
       transform: scaleY(-1);
       transform-origin: top;
       background: linear-gradient(to bottom, transparent 30%, #aa79525c);
       -webkit-background-clip: text;
       background-clip: text;
       -webkit-text-fill-color: transparent;
   }
   .group-node {
       width: 100%;
       height: 100%;
       border-width: 19px 20px 16px 10px;
       border-style: solid;
       border-image-source: url(https://patchwiki.biligame.com/images/lysk/0/00/5xlyzapkfe69krklaup1dwxxsod8f67.png);
       border-image-slice: 4% 6% 2% 3% fill;
       border-image-repeat: round;
       cursor: pointer;
       box-sizing: border-box;
   }
   .extended-read-node {
       width: 100%;
       height: 100%;
       background-image: url(https://patchwiki.biligame.com/images/lysk/5/57/cne0haqqiyjdpkww3qg54mxwfq2d3mo.png);
       background-size: 100% 100%;
   }
   #node-page-panel {
       position: absolute;
       width: 250px;
       height: 150px;
       padding: 20px 25px;
       background-image: url(https://patchwiki.biligame.com/images/lysk/c/cb/cfp9bf0jkzu4ffyxeubrf4csbmbcxq4.png);
       background-size: 100% 100%;
       display: flex;
       flex-direction: column;
       justify-content: space-between;
       align-items: flex-end;
   }
   #node-page-panel>.node-recommond {
       width: 100%;
       flex: 1;
       color: #aa7952;
       font-weight: bold;
       letter-spacing: 2px;
   }
   #node-page-panel>.node-button {
       width: 90px;
       height: 25px;
       border: none;
       font-size: 13px;
       font-weight: bold;
       padding: 0;
       background-image: url(https://patchwiki.biligame.com/images/lysk/7/7f/8cfam9dgpmzkz2om9j2bjyyqeu9q9sf.png);
       background-size: 100% 113%;
       background-position: -2px 0px;
       background-repeat: no-repeat;
       background-color: transparent;
       letter-spacing: 4px;
   }
   #container {
       width: 100%;
       height: calc(100vh - 20px);
   }
   #buttons-tip {
       position: absolute;
       width: 500px;
       background-color: #fdaeae;
       border-radius: 5px;
       text-align: center;
       padding: 10px;
       top: 0;
       left: calc((100vw - 500px) / 2);
   }
   .overlay {
       position: absolute;
       width: 80%;
       height: 80%;
       font-size: 10px;
       overflow: auto;
       top: 10%;
       left: 10%;
       background: #e1dddd;
       padding: 20px;
   }
   .right-panel {
       position: absolute;
       height: 450px;
       width: 200px;
       right: 0;
       bottom: 0;
       background-color: #ddd;
       padding: 10px;
   }
   .lf-dndpanel {
       top: 60px;
   }
   .lf-control {
       right: unset !important;
   }
   .custom-minimap {
       background: url(data:image/png;base64,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);
   }
   .input-json {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAi1JREFUWEftl7+LE0EUx98bUsQfjWAjWJizTCOolQg5rGyuEYP+A1eIlkqKzM6bHSFoICJ4hfFfuOaws/G0EUTt9ErlLCysUkSIxHk6snvEsLOzm+RMk22W3Tfv+z77nbezswhLPnDJ9WEF8NeBVqt1olqt1qy1NSFEDQDWmNmd95n5gxBibzQa7XU6ne9FpoyIGsysfGO11utp7J8p6Ha7x4bD4U0AuMHMVzIE+tbavjHmfR5IAvAyB+CgrrcHlFL3AOCBR6SPiI+J6FNW3AFk3WfmRwBwTmsdBnACUsrzQoh3WWKI+BEAmj6IrByllHOlURjAiRDRSWbOnPuyEDMBJE5cFkK89jlRqVSuttvtr6EGnRkgceIhM9/1FHmitb5z2ABnmPmzrwgi1ojoSx5EKQeklBenxYQQESKe8nT4N2ttPBE7aox5NTm2FAARHWdm17UXQtZOxxHxjet2Ivo5M4BLNMacHY/HuwBwuiiEmyZEdK/a/nROKQfSZCnlJSGEg6gUgPiBiOtE9HZh64ATiqLoGiJuhwD+fDc24jh+7hsXdCCKIkJElTyFe+qDg4huMfOWT5yZN+M4fjbXW5AHkKwFMTPLjKaTRHQ/5NBcDqTiSqmnALCZXiPiFhHdDhV38YUAJEI7ALABANta6+tFii8UoNfrHRkMBi/q9Xqj2Wz++u8ARQuWXgdCTThr4Yn+yd8PrABSB+a1OpTv3ZItHSBEfhjx1a/Z0h34DcmTODBHFVvwAAAAAElFTkSuQmCC);
   }
   .log-json {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAjpJREFUWEftlr9rFEEUx9/b4xAE/wBt1D6Faa3UWhEbvVJQiD8a/4C7me/MsmCnnRgICjYBIRKxNwgBA3baWGgUYhMEKyEc5zxvjl3Z29u9u73duE2mnJ157/O+78csU8OLG/ZPRwC1KwBAAbDzpnYMAMC+iORCOecuhWH4cZZhrbU/884Yc3/WWf89C/BLRI4R0Z/sZefc+RIAS0S0wcw3AAymgYwBaK2FiLaMMRfnoc87A+CriJwdRce8TUQdAHtF9moDAHCZiB4S0fEEIIb4EkN8yIOoDADgHBFZEbkSO9xNA/g9EfkdBIFX4k0WohIAgAtE9FxETieGmXkCIPkmIrettWtpiIUBlFJXiegFM58YMzgFIFaja62N/gGnL89bhFEUnez3+z6npyYknQHAzCsAVisBKKXAzDq3qIoBDuK2fF0pBdOiLypCIvrmnOuEYbhTuQgLot8nonXn3HoQBF5eP4iS9b7dbne63e73WtpQa/1sGNHNVNVvt1qtW71e77Pfi0fxCICZN+MZcFDbINJavx1OS99+fq0aY1YyhezfgiVmfgrgTvItvkfZKVu6DbXWu8PeP0NEL40x17ORxQpsGGPGirSowxYB8O/F3lDeZQA/swBKqbvW2ic5YLnvTGkAAJ+cc2vW2kdFec3br1OBzcFgcC+Koh+NAAB4DOBBGedxd9STAqXUNWvtq8YAyjpOtWE9CvwXgEWdzLjnh9fE715eGx6S/5HZ6QCH6Xmut+AIoAkF/gI/+G0wykYAmgAAAABJRU5ErkJggg==);
   }
   .save-json {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAATtJREFUWEftl8uRgzAMQCO1k2O2gKSAXLcbQDat5JYilgKy15y2FmvXDPYQxiAR2DCZMRcwsqRn+SMZdhs/sLH/3QNAURQHADgvgULECxH9aG1EACI6MvOXVnGqHzN/WmuvGlsRoCxLAoCKmU1Q9G3/3f/Xydi/AYCG8p6OCiIJYK1tDVdV1ToyxiTXSkoeBtKBiRCrA3jHcyD+BWAOhApAWkxjU6SJhATgd8VRAGiMMaexPhLEJIA0cq18CuIlAMM1AQB7Irq3WzmMon8OhG2oHaG2X8pHBsgRyBHIEVgjF0jnUMwVsw+iUHBIHiR5yJZPA4ylW8nxsGLKAO8TAUVZLk1/K0+V9aH0Dyk/bkN/K0LEm8rywk7OuY+6rr8fChLf8Lcj55xUAy5yj4jNn58mRnmRtRWUN78d/wJKgfAwcdVvgAAAAABJRU5ErkJggg==);
   }
   .selection-open {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAnpJREFUWEfNV7+rE0EQngkW+QssRbS0tX/vYW8haCc8bSwiiFgFye7sBn+QwkL0FeJTEGy0U0vBWFjZKogiYmFh9QolNsl9Zs7dcO+Su+RCzryFwN5kd+bb2W9mZ5jWPHjN9mkCwFr7hog2mXlLRPrj3yYAlREA570XnVtrEUD3nXNbQZbu1blzLtVpjBFmtjqfoXOydx8AAG8bjYYaTwFkvaMy/c7KF5WFfanOJEn0kBsR/MG5gnVxYeIBvTMFEe+6LkB5O1Mc+B8AZnJAo0BJuDYAkd2R2XVdQd7OwYmCKh4QkYcA9prN5s12u71XxVuFHqjCAWvtYyLaJqLfAG57728sCiJkyOlEVAWAiGwDUBDpAPCDiG557+/PA1IIoEoeEJFjAL7mjTHzRwDeOfesCEhhHpiHPP+/tfYbER2dtY+Z3yVJ0vHep49Z2Vg6CjI8KNP/gpmvi8iHokVLZ0IRuQhgd94Jw3PcEpGdmd6KwiokDG/GidFoVHiyjLE+M18Skc8rBaDKyngQjO0451q1cECVjqPhyTgazhfeL/MVEblbGwBjTIuZ75UY+MPM50Tk1cpJGDxwEsD7jPLnRHQ2a0xzAxGdqYUDgQffiegIAPHeO2vtTyI6nAPxUkROr5yEqjBkti/e+6f63el0NrSwnWHsjnPuWl6+dCIqI5YxpsPMfsrYv3B8sM87iySSZdaIyGsAp/J7Y98Q5bUVpd1u9/hwOPxERIdCNlQyXs5XXPnOSLsYl+mM0s4mWyuGDio9QGwuijxkjLnAzI+YeXcwGFzt9Xq/CjkQn8lsZ6RdTNwQi9WwTtszXqSA1QqorM6shYRVOLN2AH8BG2CPMA76kPQAAAAASUVORK5CYII=);
   }
   .selection-close {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAUlJREFUWEftlj1Ow0AQhef5DkGipE1NTzgEOQSipPbOrvER4BCROIPhADQU1KkocgQXO2TR2jjrRIpEJm7WraV938y8+QFN/GFifcoAOxkwxjyLyB2AmVJp1kT0aq197N7vAeq6vmzb9ltEbFEUbxoA3vsFAAPgipkDzJ8HmHkhIo21VtUXxhgBcLvV+w2yF8sAOQM5AzkDZ8zACsATM39OMojSEX/2SagOEEpJRBtm/tq30OJ/UtsFxpgmrvNlClGWJcdtqLeMmHlORKsYfQ/RiYsIO+fs6B44ZRekEN77ZYg8FVftggHEhYjM9omrAoTHw4m3NeQ9gA0RhbqPjKnWhl3NiegFwE3qCVUPDAxnnXN8yJg7JfjPUdr1dHgwFe8iPQRxkrPce39dVdVHrHsjIu8h8nQQDSAeRoNI4ww/5k3VEzwDHJOBH1GDWDAXw4uYAAAAAElFTkSuQmCC);
   }
   .hoverpanel-open {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAPFJREFUWEftlzEOgkAQRWcIBYl4E1t7OYKewNpWQkWYmRvYWHsCE28gl7CytvcGo1ssQSTGggWiu9UmS/Y/XrKzswgDDxw4H8YFkGXZNIqirUsrQRDsmPluM14MEJG6DLd7i0iVW02YeaGqZ0RMXELYjGdeaXJaAexi1yD1n/QA3sDvGkjTdBLH8bx+gsIwvOZ5fuvlFDDzRlX3dQBEvDDzrBeAoigYEalZQ0z18wDegDfgDfyHAWZeqeqxUQlLEUl6MfCpjfsKQFWl616wcTGRaXzf+gHzEREdAGDtEgAATiKybH0XOA5u3X5cT7O/NPAAJmqjMJ+vpdYAAAAASUVORK5CYII=);
   }
   .hoverpanel-close {
       background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAYdJREFUWEftV7tOw0AQ3LGUkh6Jno4aIZqkpQfRoIBA4h9QdLfnAv8DAtEDLaIMBeFR09FS05PGAxclkhMO8rIdEcWSC/t8O3O7nr0biIgYYw6jKDoguemfi74AtNI0vYzj+AKqWiXZLBo0FB9AzRN4JLkhIvdDSFRH+GacdVQBPMFaSz+LpIvjWEMRrLU+Qx0CzrnaOCihb40xCsD6sQWBRQb+dwZ8D/m+h8lXVHVVVd96ishFBcaYbQBXInIDYE9VP4dI7sw5d9ztvNPLMENAANy22+16kiQfWRLZlYpIvgQGVyIizUqlUm80Gu+BsWvn3E6uJQgFI/kSRdF+mqa7vU4nIn3guZXgt1QDeCW51h3/AV4IgUDK/asg+HwS6NN12SXIgpf+Ew7ovFwZzrwRzbwVeznNdDOa5lyYy264IDB/GQBwp6pbZRgTVX0mud4xJqr6kDGlf53v8rBmyyRXACx1wIEWvDMGcD5NTSedS/IIvYYiIqddkzppvJHneVMqIif+RP0Fs9tU/h3esY0AAAAASUVORK5CYII=);
   }
   .lf-dnd-item>.lf-dnd-shape {
       background-size: 100%;
   }
   .lf-node-text--auto-wrap-inner {
       font-weight: bold;
       font-size: 18px;
   }

</style>