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.icon-default.png?t=N7T8https://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的应用、以及主要的提升自我能力,望大家理性看待~

        感谢大家的支持与理解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/599063.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

性能优化-如何爽玩多线程来开发

前言 多线程大家肯定都不陌生&#xff0c;理论滚瓜烂熟&#xff0c;八股天花乱坠&#xff0c;但是大家有多少在代码中实践过呢&#xff1f;很多人在实际开发中可能就用用Async&#xff0c;new Thread()。线程池也很少有人会自己去建&#xff0c;默认的随便用用。在工作中大家对…

将excel,csv中合并块中某条记录的值应用到整个块(使用多行的值,来填充新列数据)。

背景描述 在excel中使用其它列的值&#xff0c;根据某种计算规则来填充另一列&#xff08;或新列&#xff09;很容易实现。但是如果需要根据合并块中的多行来填充列时&#xff0c;就不容易实现&#xff0c;由于对excel不是太常用&#xff0c;因此这里使用的命令行工具实现的。…

nodejs应用程序不同部署环境下的差异配置方案

一、背景 nodejs应用程序&#xff0c;不同于java语言使用分布式配置&#xff0c;当部署于不同的环境里&#xff0c;因为环境的差异&#xff0c;配置项的值也不尽相同。 最常见的差异就是数据库的连接信息&#xff0c;而代码是一份&#xff0c;不能把生产环境的信息暴露在非生产…

分布式主键ID生成策略

业务系统对分布式ID的要求 唯一性&#xff1a;在分布式系统中&#xff0c;每个节点都需要生成唯一的标识符来确保数据的唯一性。传统的单点生成ID方式无法满足分布式环境下的需求&#xff0c;而分布式ID能够在整个系统中保证每个节点生成的ID都是唯一的。 顺序性&#xff1a;某…

【TI毫米波雷达】官方工业雷达包的生命体征检测环境配置及避坑(Vital_Signs、IWR6843AOPEVM)

【TI毫米波雷达】官方工业雷达包的生命体征检测环境配置及避坑&#xff08;Vital_Signs、IWR6843AOPEVM&#xff09; 文章目录 生命体征基本介绍IWR6843AOPEVM的配置上位机配置文件避坑上位机start测试距离检测心跳检测呼吸频率检测空环境测试 附录&#xff1a;结构框架雷达基…

rocketMQ的主题

1.主题 2.队列 一个topic的queue中 的消息只能被一个消费者消费 3.分片 4.消息标识 5.生产者和消费者 工作流程

网络安全意识也是基础防御中的关键一环

在当今数字化时代&#xff0c;网络安全已经成为企业和个人生活中不可或缺的一部分。网络攻击的不断演进和加剧使得保护个人隐私、商业机密和国家安全变得尤为重要。然而&#xff0c;网络安全并非仅仅是技术层面的问题&#xff0c;更是一个综合性的挑战&#xff0c;需要广泛的参…

Linux【进程控制】总结

学习目标 首先进程控制分为四大部分&#xff1a;进程创建、进程退出、进程等待、进程替换&#xff1b; 第一步&#xff1a;学习如何来创建一个进程&#xff0c;一般我们会使用fork函数来创建子进程&#xff0c;创建子进程之后&#xff0c;就要去探索子进程与父进程的相关联系&a…

Visual Studio 2022-C语言如何防止头文件多次引入

头文件的包含 本地⽂件包含 #include "filename" 查找策略&#xff1a;先在源⽂件所在⽬录下查找&#xff0c;如果该头⽂件未找到&#xff0c;编译器就像查找库函数头⽂件⼀样在 标准位置查找头⽂件。 如果找不到就提⽰编译错误。 Linux环境的标准头⽂件的路径&…

书生·浦语训练营二期第三次笔记-茴香豆:搭建你的 RAG 智能助理

RAG学习文档1&#xff1a; https://paragshah.medium.com/unlock-the-power-of-your-knowledge-base-with-openai-gpt-apis-db9a1138cac4 RAG学习文档2: https://blog.demir.io/hands-on-with-rag-step-by-step-guide-to-integrating-retrieval-augmented-generation-in-llms-a…

yolov9直接调用zed相机实现三维测距(python)

yolov9直接调用zed相机实现三维测距&#xff08;python&#xff09; 1. 相关配置2. 相关代码2.1 相机设置2.2 测距模块2.2 实验结果 相关链接 此项目直接调用zed相机实现三维测距&#xff0c;无需标定&#xff0c;相关内容如下&#xff1a; 1. yolov4直接调用zed相机实现三维测…

90天玩转Python—05—基础知识篇:Python基础知识扫盲,使用方法与注意事项

90天玩转Python系列文章目录 90天玩转Python—01—基础知识篇:C站最全Python标准库总结 90天玩转Python--02--基础知识篇:初识Python与PyCharm 90天玩转Python—03—基础知识篇:Python和PyCharm(语言特点、学习方法、工具安装) 90天玩转Python—04—基础知识篇:Pytho…