Svg Flow Editor 原生svg流程图编辑器(五)
系列文章
Svg Flow Editor 原生svg流程图编辑器(一)
Svg Flow Editor 原生svg流程图编辑器(二)
Svg Flow Editor 原生svg流程图编辑器(三)
Svg Flow Editor 原生svg流程图编辑器(四)
Svg Flow Editor 原生svg流程图编辑器(五)
协同编辑
对协同这块已经写了很多篇文章了,如果还是不了解,可以看看之前的文章哈,我们还是使用Yjs实现协同的底层支持,Websocket 还是以插件的形式支持:
这次的协同,并没有直接使用 y-websocket 插件支持,而是自己实现了websocket 相关的连接、异常、重连操作,y-websocket 插件无非就是内部对协同数据做了合并,监听消息后触发 update更新:
我们手动实现,只需要对协同的数据进行底层的一致性冲突处理、合并就可以达到一样的目的,如下:
在发送数据之前,需要先获取本地的所有yjs数据状态 state,携带着一起发送给 websocket 服务器,其他客户端收到后,先执行解析合并操作,然后再从最终结果解析数据,以达到数据一致性的目的。下列就是 yjs 的核心方法:
发送数据之前,进行数据映射:
此类,我们就可以不基于 y-websocket插件,自身实现websocket服务,也能使用yjs实现协同,保持数据一致性,关键就是使用 encodeStateAsUpdate 进行本地数据获取,applyUpdate 进行应用更新,详细解释:
Document Updates | Yjs DocsHow to sync documents with other peers.https://docs.yjs.dev/api/document-updates#syncing-clients 效果如下:
搜索替换
之前我们文本的实现方案是创建 contenteditable,然后移出时,创建了svg text,使得文本能显示在元件上,但是这样有些问题,不能进行搜索替换,因为svg的样式与css样式还不一致,因此在搜索结果的高亮显示上还有些难以实现。
因此,我们替换方案为直接使用 contenteditable,移出时,控制样式 point-event:none;user-select:none即可,在搜索高亮中,替换字符串为 b 标签,并加上css 控制,即可实现。
封装搜索替换组件,并绑定快捷键 Ctrl + F
// 可以用 getSelection 获取用户目前选中的文本
const { anchorOffset, focusOffset, baseNode } = window.getSelection() as Selection;// 搜索的核心就是遍历目前页面上的文本,判定内容是否包含了搜索框文本
editorBox.querySelectorAll(".sf-editor-box-graphs-main-contenteditable").forEach((item) =>{// item 是 contenteditableBox 里面的 div 才是内容const editor = item.querySelector("div") as HTMLDivElement;editor.innerHTML = editor.innerHTML.replace(/<|>|\/|b|span/g, "");const findFlag = editor.innerText.includes(this.keyword);findFlag &&this.keyword &&this.conformList.push(item as HTMLDivElement);
});
在 数量上,则是记录全局变量 index all,all是搜索匹配到的所有文本项,index 则是匹配到的当前索引,替换的方案就是直接 replace 即可,实现效果如下:
表格
本来想用 luckysheet 实现表格的,但是想了想,还是太冗余了,流程图中的表格尽量简单就好了,主要做数据展示,不涉及复杂的计算,因此,还是用原生的table 实现吧。
this.table = draw.createHTMLElement("table") as HTMLTableElement;this.table.classList.add("sf-editor-table");// 创建头部 headprivate createHead(draw: Draw) {const thead = draw.createHTMLElement("thead");const tr = draw.createHTMLElement("tr");for (let i = 0; i < this.col; i++) {const th = draw.createHTMLElement("th");const div = draw.createHTMLElement("div");div.innerText = `标题${i + 1}`;th.appendChild(div);tr.appendChild(th);}thead.appendChild(tr);this.table.appendChild(thead);}// 创建 tbodyprivate createBody(draw: Draw) {const body = draw.createHTMLElement("tbody");for (let i = 0; i < this.row; i++) {const tr = draw.createHTMLElement("tr");for (let i = 0; i < this.col; i++) {const td = draw.createHTMLElement("td");const div = draw.createHTMLElement("div");td.appendChild(div);tr.appendChild(td);}body.appendChild(tr);}this.table.appendChild(body);}
文本编辑上,使用 contenteditable 实现:
// 初始化 双击编辑事件private initEvent() {const divs = this.table.querySelectorAll("div");divs.forEach((item) => {item.addEventListener("dblclick", () => {item.setAttribute("contenteditable", "true");item.focus();this.setRange(item);item.addEventListener("blur", () =>divs.forEach((i) => i.removeAttribute("contenteditable")));});});}
效果与markdown的表格类似:
图片导出
导出使用的是html2canva库,在一些细节的处理上,需要看官网的说明,比如处理跨域图片问题,宽高尺寸问题,还有的就是循环遍历导致截图过慢问题等,可以看出,每次使用插件导出图片,都会从 HTML head 开始遍历DOM结构,在我们的项目中影响不大,但是用户的环境,可能有很多的dom,肯定会影响效率,我们导出图片仅需要在 sf-editor-box 中做处理即可,因此,需要使用 ignoreElements 进行元素过滤。
没有做过滤,整体的时间大概在435毫秒:
const option = {ignoreElements: (ele: HTMLElement) => {// this.editorBox compareDocumentPosition// 1: 没有关系,这两个节点不属于同一个文档// 2: 第一节点(P1)位于第二个节点后(P2)// 4: 第一节点(P1)定位在第二节点(P2)前// 8: 第一节点(P1)位于第二节点内(P2)// 16:第二节点(P2)位于第一节点内(P1)// 还可能是上诉值的和!返回 20 意味着在 p2 在 p1 内部(16),并且 p1 在 p2 之前(4)const box = this.draw.getEditorBox();const index = box.compareDocumentPosition(ele);if ([1, 2, 4].includes(index)) return false;},};
优化后的平均耗时 250毫秒,如果在大体量DOM结构中,这个优化会更加明显。
/*** 利用 html2canvas 截图* 1. ignoreElements 处理截图慢问题: (element) => false 与 root 进行位置比较* 2. x y width height 处理最佳宽高,不出现大量空白* 3. proxy、useCORS、allowTaint 处理跨域图片问题* 4. backgroundColor 支持透明、白色背景(设置null为透明)* @param filetype 保存的文件类型,支持 png svg jpg json*/public async screenShot(filetype: string) {await nextTick();const box = this.draw.getEditorBox();// const width = box.clientWidth;// const height = box.clientHeight;this.draw.showLoading();// 处理x y height width - 相对于 editor box 的位置关系var minx = 0;var miny = 0;var maxx = 0;var maxy = 0;// 获取 editor box 的宽高const graphlist = this.draw.getGraphEvent().getAllGraphMain();if (graphlist.length) {const firstGraph = new Graph(this.draw,graphlist[0].getAttribute("graphid") as string);minx = firstGraph.getX();miny = firstGraph.getY();graphlist.forEach((item) => {// 需要得到最小和最大位置的graphconst nodeID = item.getAttribute("graphid") as string;const graph = new Graph(this.draw, nodeID);minx = Math.min(minx, graph.getX());miny = Math.min(miny, graph.getY());maxx = Math.max(maxx, graph.getX() + graph.getWidth() + 20);maxy = Math.max(maxy, graph.getY() + graph.getHeight() + 20);});}const option = {x: minx,y: miny,width: maxx - minx,height: maxy - miny,ignoreElements: (ele: HTMLElement) => {// this.editorBox compareDocumentPosition// 1: 没有关系,这两个节点不属于同一个文档// 2: 第一节点(P1)位于第二个节点后(P2)// 4: 第一节点(P1)定位在第二节点(P2)前// 8: 第一节点(P1)位于第二节点内(P2)// 16:第二节点(P2)位于第一节点内(P1)// 还可能是上诉值的和!返回 20 意味着在 p2 在 p1 内部(16),并且 p1 在 p2 之前(4)const index = box.compareDocumentPosition(ele);if ([1, 2, 4].includes(index)) return false;},};// @ts-ignoreconst canvas = await html2canvas(this.draw.getEditorBox(), option);// base64 使用服务器存储方案 const base64 = canvas.toDataURL("image/png");canvas.toBlob((b: File) => {const url = toBlob(b, "image/png") as string;const a = this.draw.createHTMLElement("a");a.setAttribute("href", url);a.setAttribute("download", "测试");this.draw.hideLoading();window.open(url);// a.click(); // 触发下载a.remove();});}
总结
至此,该实现的功能基本上都已经具备雏形了,后面就不再更新文章咯,但是还是会持续更新这个库,大家有什么想法,需要什么BUG,都可以在git、文章下留言,我会持续关注大家的意见,维护这个库。
即将发布的 1.0.15 版本,是1.0版本的最后一版,后续的版本将更替为 1.1 ,主要实现协同、相关工具类、以及关键的 history历史记录。目前市面上也有很多成熟的产品,做这个主要不是为了超越他们,而是熟悉流程图的底层实现、TypeScript的应用、以及主要的提升自我能力,望大家理性看待~
感谢大家的支持与理解!
相关文章:

Svg Flow Editor 原生svg流程图编辑器(五)
系列文章 Svg Flow Editor 原生svg流程图编辑器(一) Svg Flow Editor 原生svg流程图编辑器(二) Svg Flow Editor 原生svg流程图编辑器(三) Svg Flow Editor 原生svg流程图编辑器(四…...

数字晶体管选型参数,结构原理,工艺与注意问题总结
🏡《总目录》 目录 1,概述2,工作原理2.1,AND 门(与门),2.2,OR 门(或门):2.3,NOT 门(非门):2.4,NAND 门(与非门):2.5,NOR 门(或非门):3,结构特点3.1,TTL(Transistor-Transistor Logic)晶体管...

lua学习笔记9(字典的学习)
print("********************字典的学习***********************") a{["凌少"]"傻逼",["我"]"天才",["age"]24,["daihao"]114514,["8848"]20000} --访问单个变量 print(a["凌少"])…...

第六篇: 3.5 性能效果 (Performance)- IAB/MRC及《增强现实广告效果测量指南1.0》
翻译计划 第一篇概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇 广告效果测量定义和其他矩阵之- 3.1 广告印象(AD Impression)第三篇 广告效果测量定义和其他矩阵之- 3.2 可见性 (Viewability…...

mysql学习笔记NO.2
Java操作数据库、表笔记 1.创建数据库 创建数据库的步骤如下: 导入所需的Java数据库连接驱动(如MySQL驱动)。使用JDBC连接到数据库。执行SQL语句创建数据库。 import java.sql.Connection; import java.sql.DriverManager; import java.…...

C++11:lambda表达式 包装器
C11:lambda表达式 & 包装器 lambda表达式包装器functionbind lambda表达式 在C98中,如果想对一个结构体数组使用sort排序,那么我们就需要自己些仿函数。 比如以下结构体: struct Goods {string _name; // 名字double _pric…...

Node.js HTTP/2 CONTINUATION 拒绝服务漏洞(CVE-2024-27983)
Node.js 是开源、跨平台的 JavaScript 运行时环境。CONTINUATION泛洪攻击被发现存在于多个HTTP/2协议实现中。 在受影响版本中,由于Node.js针对HTTP/2协议的实现不当,未正确处理多个CONTINUATION帧的情况,在node::http2::Http2Session::~Htt…...

YOLOV8 + 双目测距
YOLOV8 双目测距 1. 环境配置2. 测距流程和原理2.1 测距流程2.2 测距原理 3. 代码部分解析3.1 相机参数stereoconfig.py3.2 测距部分3.3 主代码yolov8-stereo.py 4. 实验结果4.1 测距4.2 测距跟踪4.3 测距跟踪分割4.4 视频展示 相关文章 1. YOLOv5双目测距(python&…...

前端:SVG绘制流程图
效果 代码 html代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>SVG流程图示例</title><style>/* CSS 样式 */</style><script src"js/index.js"></script…...

【Linux系列】如何确定当前运行的是 RHEL 9 还是 RHEL 8?
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

vscode开发java的插件和配置
推荐插件 .vscode/extensions.json {"recommendations": ["redhat.fabric8-analytics","ms-azuretools.vscode-docker","vscjava.vscode-java-pack","eamodio.gitlens","obkoro1.korofileheader","redhat.j…...

Mysql启动报错:本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止
Mysql启动报错:本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止 文章目录 Mysql启动报错:本地计算机上的mysql服务启动后停止,某些服务在未由其他服务或程序使用时将自动停止1. 备份mysql的data文件夹2. 重新构建 Wind…...

WPF程序添加托盘图标
程序添加托盘图标 UI层 //添加handycontrol的引用xmlns:hc"https://handyorg.github.io/handycontrol"//添加NotifyIcon图标 实现单击 双击 二级菜单点击功能<hc:NotifyIconText"通知"Token"Info"><hc:NotifyIcon.ContextMenu><…...

工业4g路由器联网后迅速掉线是什么原因?
工业4G路由器连接上网后迅速掉线可能是由多种因素造成的。以下是一些建议的检查和解决步骤: 1、信号问题: 信号强度:检查工业路由器信号强度指示灯,如果信号弱,尝试移动路由器位置或添加外部天线来增强信号。 网络拥…...

腾讯云4核8G服务器12M带宽646元1年零3个月,4C8G使用场景说明
腾讯云4核8G服务器多少钱?腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月,活动页面 txybk.com/go/txy 活动链接打开如下图所示: 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器,详细配置为:轻量4核…...

java - 读取配置文件
文章目录 1. properties2. XML(1) dom4j(2) XPath 1. properties // 创建properties对象用于读取properties文件Properties properties new Properties();properties.load(new FileReader("src/main/resources/test.properties"));String name properties.getPrope…...

Ubuntu22.04平台编译完美解决问题“error: GLSL 4.5 is not supported.”【GLSL(OpenGL着色器语言)】
GLSL介绍 GLSL(OpenGL着色器语言)是用于编写OpenGL着色器程序的语言。GLSL 4.5 是 GLSL 的一个版本,引入了许多新的特性和改进,旨在提高着色器编程的灵活性和性能。GLSL 4.5 工具通常是用于编写、调试和优化 GLSL 4.5 着色器代码…...

数据结构之搜索二叉树与关联性容器初接触
一、搜索二叉树 1>、前言 1. map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构 2. 二叉搜索树的特性了解,有助于更好的理解map和set的特性。 2>、概念 二叉搜索树又称二叉排序树,它或者是一棵空树,或者…...

C语言整数和小数的存储
1.整数在内存中的存储 计算机使用二进制进行存储、运算,整数在内存中存储使用的是二进制补码 1.1原码、反码、补码 整数的2进制表⽰⽅法有三种,即 原码、反码和补码 三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”&am…...

Games101Homework【6】Acceleration structure(Including framework analysis)
Code Analysis: friend: C中友元(友元函数和友元类)的用法和功能_friend class a<b>-CSDN博客 [C:不如Coding](11):友元函数与友元类_哔哩哔哩_bilibili Here is a simple…...

应用运维文档1
统一nginx接入配置指南 Nginx配置规范 1:不带微服务编码上下文至后端,以metadata-ui为例 location段配置信息,location配置中维护微服务编码上下文信息 # app_code: metadata-ui 流水线名称: metadata-ui location ~ ^/metadata-ui/(?P.*) {set $app_code metadata-ui;p…...

手机如何在线制作gif?轻松一键在线操作
现在大家都喜欢使用手机来拍摄记录有趣的事物,但是时间长了手机里的视频越来越多导致手机存储空间不够了,这些视频又不想删除时应该怎么办呢?这个很简单,下面就给大家分享一款不用下载手机就能操作的视频转gif网站-GIF中文网&…...

ChatGPT 在做什么,为什么有效?
原文:What Is ChatGPT Doing … and Why Does It Work? 译者:飞龙 协议:CC BY-NC-SA 4.0 序言 这本简短的书试图从第一原理解释 ChatGPT 是如何工作的。在某种程度上,这是关于技术的故事。但它也是关于科学的故事。以及关于哲学…...

Linux实验2 初步使用shell
一:实验目的 学习Linux下的文件系统结构,了解最基本的Linux下的shell命令操作,例如ls, cd, cat等各种指令操作。 学习vim编辑器的使用方式,学习如何使用ssh连接远程服务器。 二:实验内容 1.请指出下面每…...

甘特图/横道图制作技巧 - 任务组
在甘特图中通过合理的任务分组可以让项目更加清晰,修改也更方便。 列如下面的甘特图一眼不太容易看清楚整体的进度。或者需要把所有的任务整体的延迟或者提前只能这样一个一个的任务调整,就比较麻烦。 通过给任务分组,看这上面整体的进度就…...

Web题记
反序列化补充知识: private变量会被序列化为:\x00类名\x00变量名 protected变量会被序列化为: \x00\*\x00变量名 public变量会被序列化为:变量名web254 这个逻辑不难,自己刚看的时候还奇怪是不是自己哪里想错了,因为…...

学习java第三十六天
Spring 官网列出的 Spring 的 6 个特征: 核心技术 :依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。 测试 :模拟对象,…...

0205矩阵分块法-矩阵及其运算-线性代数
文章目录 1 分块矩阵的定义2 分块矩阵的运算(性质)3 按列分块与按行分块 结语 1 分块矩阵的定义 将矩阵A用若干条纵线和横线分成许多个小矩阵,每一个小矩阵称为A的子快,以子块为元素的形式上的矩阵称为分块矩阵。 2 分块矩阵的运算…...

1、java语法入门(找工作版)
文章目录 一、Java简介二、Java常量与变量1、标识符2、关键字3、变量4、类的命名规则5、数据类型6、基本数据类型字面值7、变量的定义与初始化8、ASCII码和Unicode编码9、转义字符10、类型转换11、常量 三、Java运算符1、算术运算符2、赋值运算符3、关系运算符4、逻辑运算符5、…...

arm的状态寄存器
目录 一、arm 的 PSRs二、CPSR2.1 CPSR_cxsf 三、SPSR四、APSR 一、arm 的 PSRs arm 中有很多程序状态寄存器(Program Status Registers,PSRs)用于存储处理器的状态信息,包括 CPSR\SPSR\FPSR\APSR 等: CPSRÿ…...