UNPKG

@meta2d/core

Version:

@meta2d/core: Powerful, Beautiful, Simple, Open - Web-Based 2D At Its Best .

1,264 lines 240 kB
import { clearIframes, commonAnchors, commonPens, cube, updateIframes, reset, updateFormData } from './diagrams'; import { Canvas } from './canvas'; import { calcInView, calcTextDrawRect, calcTextLines, calcTextRect, facePen, formatAttrs, getAllChildren, getFromAnchor, getParent, getToAnchor, getWords, LockState, PenType, renderPenRaw, setElemPosition, connectLine, nearestAnchor, setChildValue, isAncestor, isShowChild, CanvasLayer, validationPlugin, setLifeCycleFunc, getAllFollowers, isInteraction, calcWorldAnchors, isDomShapes, defaultFormat, findOutliersByZScore, } from './pen'; import { rotatePoint } from './point'; import { clearStore, EditType, globalStore, register, registerAnchors, registerCanvasDraw, useStore, } from './store'; import { formatPadding, loadCss, s8, valueInArray, valueInRange, } from './utils'; import { calcCenter, calcRelativeRect, calcRightBottom, getRect, rectInRect, } from './rect'; import { deepClone } from './utils/clone'; import { EventAction } from './event'; import { ViewMap } from './map'; import * as mqtt from 'mqtt/dist/mqtt.min.js'; import pkg from '../package.json'; import { lockedError } from './utils/error'; import { Scroll } from './scroll'; import { getter } from './utils/object'; import { d, getCookie, getMeta2dData, getToken, queryURLParams } from './utils/url'; import { HotkeyType } from './data'; import { Message, messageList } from './message'; import { closeJetLinks, connectJetLinks, getSendData, sendJetLinksData } from './utils/jetLinks'; import { le5leTheme } from './theme'; const echartReg = /^echarts/; export class Meta2d { store; canvas; websocket; mqttClient; websockets; mqttClients; eventSources; penPluginMap = new Map(); socketFn; events = {}; map; mapTimer; constructor(parent, opts = {}) { this.store = useStore(s8()); this.setOptions(opts); this.setDatabyOptions(opts); this.init(parent); this.register(commonPens()); this.registerCanvasDraw({ cube }); this.registerAnchors(commonAnchors()); globalThis.meta2d = this; this.initEventFns(); this.store.emitter.on('*', this.onEvent); } facePen = facePen; getWords = getWords; calcTextLines = calcTextLines; calcTextRect = calcTextRect; calcTextDrawRect = calcTextDrawRect; /** * @deprecated 改用 beforeAddPens */ get beforeAddPen() { return this.canvas.beforeAddPen; } /** * @deprecated 改用 beforeAddPens */ set beforeAddPen(fn) { this.canvas.beforeAddPen = fn; } get beforeAddPens() { return this.canvas.beforeAddPens; } set beforeAddPens(fn) { this.canvas.beforeAddPens = fn; } get beforeAddAnchor() { return this.canvas.beforeAddAnchor; } set beforeAddAnchor(fn) { this.canvas.beforeAddAnchor = fn; } get beforeRemovePens() { return this.canvas.beforeRemovePens; } set beforeRemovePens(fn) { this.canvas.beforeRemovePens = fn; } get beforeRemoveAnchor() { return this.canvas.beforeRemoveAnchor; } set beforeRemoveAnchor(fn) { this.canvas.beforeRemoveAnchor = fn; } setOptions(opts = {}) { if (opts.grid !== undefined || opts.gridColor !== undefined || opts.gridSize !== undefined) { // this.setGrid({ // grid: opts.grid, // gridColor: opts.gridColor, // gridSize: opts.gridSize, // }); this.canvas && (this.canvas.canvasTemplate.bgPatchFlags = true); } if (opts.rule !== undefined || opts.ruleColor !== undefined || opts.ruleOptions !== undefined) { // this.setRule({ // rule: opts.rule, // ruleColor: opts.ruleColor, // }); this.store.patchFlagsTop = true; if (opts.ruleOptions) { if (this.store.options?.ruleOptions) { Object.assign(this.store.options.ruleOptions, opts.ruleOptions); opts.ruleOptions = this.store.options.ruleOptions; } } } if (opts.background !== undefined) { this.canvas && (this.canvas.canvasTemplate.bgPatchFlags = true); } if (opts.resizeMode !== undefined) { if (!opts.resizeMode) { this.canvas.hotkeyType = HotkeyType.None; } } if (opts.width !== undefined || opts.height !== undefined) { this.canvas && (this.canvas.canvasTemplate.bgPatchFlags = true); if (this.canvas && this.canvas.canvasTemplate.canvas.style.backgroundImage) { this.canvas.canvasTemplate.canvas.style.backgroundImage = ''; } } this.store.options = Object.assign(this.store.options, opts); if (opts.roles && this.store.data.pens?.length) { for (const pen of this.store.data.pens) { calcInView(pen); } } if (this.canvas && opts.scroll !== undefined) { if (opts.scroll) { !this.canvas.scroll && (this.canvas.scroll = new Scroll(this.canvas)); this.canvas.scroll.show(); } else { this.canvas.scroll && this.canvas.scroll.hide(); } } this.canvas?.initGlobalStyle(); } getOptions() { return this.store.options; } /** * @description * @author Joseph Ho * @date 21/02/2025 * @param {string} themeName 主题名 * @param {object} theme 主题变量字符串数组 * @returns {*} * @memberof Meta2d */ registerTheme(themeName, theme) { // 校验数据 if (!Array.isArray(theme)) { return; } // 写一个正则,中间必须有且仅有1个冒号,结尾不能有分号,符合"A:B"的形式,冒号两边必须非空,允许有空格 const regex = /^\s*\S+\s*:\s*\S+\s*$/; const ret = theme.every(el => regex.test(el)); if (!ret) { return; } const obj = {}, newTheme = []; for (let i = 0; i < theme.length; i++) { const item = theme[i]; const kvs = item.split(":"); const kvs0 = kvs[0].trim(); const kvs1 = kvs[1].trim(); newTheme.push([kvs0, kvs1].join(":")); obj[kvs0] = kvs1; } le5leTheme.addTheme(themeName, newTheme); this.store.theme[themeName] = obj; } setTheme(theme) { this.store.data.theme = theme; this.setBackgroundColor(this.store.theme[theme].background); this.canvas.parentElement.style.background = this.store.theme[theme].parentBackground; this.setOptions({ ruleColor: this.store.theme[theme].ruleColor, ruleOptions: this.store.theme[theme].ruleOptions, }); // 更新全局的主题css变量 if (!(this.store.options.themeOnlyCanvas || this.store.data.themeOnlyCanvas)) { this.store.data.color = this.store.theme[theme].color; le5leTheme.updateCssRule(this.store.id, theme); this.canvas.initGlobalStyle(); for (let i = 0; i < this.store.data.pens.length; i++) { const pen = this.store.data.pens[i]; // 调用pen的主题设置函数,如果单个pen有主题的自定义设置的话 pen.setTheme && pen.setTheme(pen, this.store.styles); } } this.render(); } setDatabyOptions(options = {}) { const { color, activeColor, activeBackground, grid, gridColor, gridSize, fromArrow, toArrow, rule, ruleColor, textColor, x = 0, y = 0, } = options; this.setRule({ rule, ruleColor }); this.setGrid({ grid, gridColor, gridSize, }); this.store.data = Object.assign(this.store.data, { textColor, color, activeColor, activeBackground, fromArrow, toArrow, x, y, }); } init(parent) { if (typeof parent === 'string') { this.canvas = new Canvas(this, document.getElementById(parent), this.store); } else { this.canvas = new Canvas(this, parent, this.store); } this.canvas.initGlobalStyle(); this.resize(); this.canvas.listen(); // 创建主题样式表 // if(this.store.data.theme){ le5leTheme.createThemeSheet(this.store.data.theme, this.store.id); // } } initEventFns() { this.events[EventAction.Link] = (pen, e) => { if (window && e.value && typeof e.value === 'string') { let url = e.value; if (url.includes('${')) { let keys = url.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys) { keys?.forEach((key) => { url = url.replace(`\${${key}}`, getter(pen, key) || this.getDynamicParam(key)); }); } } window.open(url, e.params ?? '_blank'); return; } console.warn('[meta2d] Link param is not a string'); }; this.events[EventAction.SetProps] = (pen, e) => { // TODO: 若频繁地触发,重复 render 可能带来性能问题,待考虑 const value = e.value; if (value && typeof value === 'object') { const pens = e.params ? this.find(e.params) : this.find(pen.id); const _value = {}; for (let key in value) { if (value[key]?.id) { _value[key] = this.store.pens[value[key].id]?.[value[key].key]; } else { if (typeof value[key] === 'string' && value[key].includes('${')) { let __value = value[key]; let keys = __value.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys) { keys.forEach((key) => { __value = __value.replace(`\${${key}}`, getter(pen, key) || this.getDynamicParam(key)); }); } _value[key] = __value; } else if (typeof value[key] === 'string' && ((value[key].startsWith('{') && value[key].endsWith('}')) || (value[key].startsWith('[') && value[key].endsWith(']')))) { try { _value[key] = JSON.parse(value[key]); } catch (e) { _value[key] = value[key]; } } else { _value[key] = value[key]; } } } pens.forEach((pen) => { if (_value.hasOwnProperty('visible')) { if (pen.visible !== _value.visible) { this.setVisible(pen, _value.visible); } } this.setValue({ id: pen.id, ..._value }, { render: false, doEvent: false }); }); this.render(); return; } console.warn('[meta2d] SetProps value is not an object'); }; this.events[EventAction.StartAnimate] = (pen, e) => { let _pen = pen; if (e.value) { _pen = this.findOne(e.value); } if (this.store.animates.has(_pen) && !_pen.calculative.pause && _pen.animateName === e.params) { return; } if (e.targetType && e.params) { this.startAnimate(e.value || [pen], e.params); return; } if (!e.value || typeof e.value === 'string') { this.startAnimate(e.value || [pen]); return; } console.warn('[meta2d] StartAnimate value is not a string'); }; this.events[EventAction.PauseAnimate] = (pen, e) => { if (!e.value || typeof e.value === 'string') { this.pauseAnimate(e.value || [pen]); return; } console.warn('[meta2d] PauseAnimate value is not a string'); }; this.events[EventAction.StopAnimate] = (pen, e) => { if (!e.value || typeof e.value === 'string') { if (e.value) { let _pen = this.findOne(e.value); if (!this.store.animates.has(_pen)) { return; } } else { if (!this.store.animates.has(pen)) { return; } } this.stopAnimate(e.value || [pen]); return; } console.warn('[meta2d] StopAnimate event value is not a string'); }; this.events[EventAction.StartVideo] = (pen, e) => { if (!e.value || typeof e.value === 'string') { this.startVideo(e.value || [pen]); return; } console.warn('[meta2d] StartVideo value is not a string'); }; this.events[EventAction.PauseVideo] = (pen, e) => { if (!e.value || typeof e.value === 'string') { this.pauseVideo(e.value || [pen]); return; } console.warn('[meta2d] PauseVideo value is not a string'); }; this.events[EventAction.StopVideo] = (pen, e) => { if (!e.value || typeof e.value === 'string') { this.stopVideo(e.value || [pen]); return; } console.warn('[meta2d] StopVideo event value is not a string'); }; this.events[EventAction.JS] = (pen, e, params) => { if (e.value && !e.fn) { try { if (typeof e.value !== 'string') { throw new Error('[meta2d] Function value must be string'); } const fnJs = e.value; e.fn = new Function('pen', 'params', 'context', fnJs); } catch (err) { console.error('[meta2d]: Error on make a function:', err, 'code:', e.value); } } e.fn?.(pen, params || e.params, { meta2d: this, eventName: e.name }); }; this.events[EventAction.GlobalFn] = (pen, e) => { if (typeof e.value !== 'string') { console.warn('[meta2d] GlobalFn value must be a string'); return; } if (globalThis[e.value]) { globalThis[e.value](pen, e.params); } }; this.events[EventAction.Emit] = (pen, e) => { if (typeof e.value !== 'string') { console.warn('[meta2d] Emit value must be a string'); return; } this.store.emitter.emit(e.value, { pen, params: e.params, eventName: e.name, }); }; this.events[EventAction.SendPropData] = (pen, e) => { const value = deepClone(e.value); if (value && typeof value === 'object') { const _pen = e.params ? this.findOne(e.params) : pen; for (let key in value) { if (value[key] === undefined || value[key] === '') { value[key] = _pen[key]; } } value.id = _pen.id; this.doSendDataEvent(value, e.extend); return; } console.warn('[meta2d] SendPropData value is not an object'); }; this.events[EventAction.SendVarData] = (pen, e) => { const value = deepClone(e.value); if (value && typeof value === 'object') { const _pen = e.params ? this.findOne(e.params) : pen; let array = []; for (let key in value) { let obj = { dataId: key, value: value[key], }; if (!obj.value) { let oneForm = _pen.form.find((_item) => _item.dataIds && _item.dataIds.dataId === obj.dataId); if (oneForm) { obj.value = _pen[oneForm.key]; } } array.push(obj); } this.doSendDataEvent(array, e.extend); return; } console.warn('[meta2d] SendVarData value is not an object'); }; this.events[EventAction.Navigator] = (pen, e) => { if (e.value && typeof e.value === 'string') { this.navigatorTo(e.value); } }; this.events[EventAction.Dialog] = (pen, e) => { if (e.params && typeof e.params === 'string') { let url = e.params; if (e.params.includes('${')) { let keys = e.params.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys) { keys?.forEach((key) => { url = url.replace(`\${${key}}`, getter(pen, key) || this.getDynamicParam(key)); }); } } Object.keys(e.extend).forEach((key) => { if (!['x', 'y', 'width', 'height'].includes(key)) { if (url.indexOf('?') !== -1) { url += `&${key}=${e.extend[key]}`; } else { url += `?${key}=${e.extend[key]}`; } } }); let data = this.getEventData(e.list, pen); if (Object.keys(data).length) { data = null; } this.canvas.dialog.show(e.value, url, e.extend, data); } }; this.events[EventAction.SendData] = (pen, e) => { if (e.data?.length) { const value = this.getSendData(e.data, pen); if (pen.formId && pen.formData) { //表单数据 Object.assign(value, pen.formData); } this.sendDataToNetWork(value, pen, e); return; } if (e.list?.length) { // if (e.targetType === 'id') { if (e.network && e.network.protocol === 'ADIIOT') { const list = getSendData(this, pen, e); if (list.length) { sendJetLinksData(this, list); } return; } const value = this.getEventData(e.list, pen); if (pen.deviceId) { value.deviceId = pen.deviceId; } if (pen.formId && pen.formData) { //表单数据 Object.assign(value, pen.formData); } this.sendDataToNetWork(value, pen, e); return; // } } const value = deepClone(e.value); if (value && typeof value === 'object') { if (e.targetType === 'id') { const _pen = e.params ? this.findOne(e.params) : pen; for (let key in value) { if (value[key] === undefined || value[key] === '') { value[key] = getter(_pen, key); } else if (typeof value[key] === 'string' && value[key]?.indexOf('${') > -1) { let keys = value[key].match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys?.length) { value[key] = getter(_pen, key[0]) ?? this.getDynamicParam(keys[0]); } } } // value.id = _pen.id; if (_pen.deviceId) { value.deviceId = _pen.deviceId; } this.sendDataToNetWork(value, pen, e); return; } } }; this.events[EventAction.PostMessage] = (pen, e) => { if (typeof e.value !== 'string') { console.warn('[meta2d] Emit value must be a string'); return; } const _pen = e.params ? this.findOne(e.params) : pen; if (_pen.name !== 'iframe' || !_pen.iframe) { console.warn('不是嵌入页面'); return; } let params = queryURLParams(_pen.iframe.split('?')[1]); let value = this.getSendData(e.data, pen); if (e.list) { value = this.getEventData(e.list, pen); } _pen.calculative.singleton.div.children[0].contentWindow.postMessage(JSON.stringify({ name: e.value, id: params.id, data: value, }), '*'); return; }; this.events[EventAction.PostMessageToParent] = (pen, e) => { if (typeof e.value !== 'string') { console.warn('[meta2d] Emit value must be a string'); return; } let value = this.getSendData(e.data, pen); if (e.list) { value = this.getEventData(e.list, pen); } window.parent.postMessage(JSON.stringify({ name: e.value, data: value }), '*'); return; }; this.events[EventAction.Message] = (pen, e, params) => { let theme = e.params; let content = e.value; if (!content && params && params.type === 'http') { content = params.error.statusText; if (!theme) { theme = 'error'; } } this.message({ theme, content, ...e.extend, }); }; } getSendData(data, cpen) { const value = {}; data?.forEach((item) => { if (item.prop) { if (item.id && item.id !== '固定值') { const pen = this.findOne(item.id); value[item.prop] = getter(pen, item.key); // pen[item.key]; } else { if (typeof item.value === 'string' && item.value.includes('${')) { let _value = item.value; let keys = _value.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys) { keys.forEach((key) => { _value = _value.replace(`\${${key}}`, getter(cpen, key) || this.getDynamicParam(key)); }); } value[item.prop] = _value; } else { value[item.prop] = this.convertType(item.value, item.type); } } } }); return value; } convertType(value, type) { if (typeof value === 'string') { if (['switch', 'bool', 'boolean'].includes(type)) { if (value === 'false') { return false; } else if (value === 'true') { return true; } } else if (['integer', 'number', 'int', 'enum', 'double', 'float'].includes(type)) { if (!isNaN(Number(value))) { return Number(value); } } } else if (typeof value === 'object') { let bodyStr = JSON.stringify(value); let keys = bodyStr.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys?.length) { for (let i = 0; i < keys.length; i++) { bodyStr = bodyStr.replace(`\${${keys[i]}}`, this.getDynamicParam(keys[i])); } } return JSON.parse(bodyStr); } return value; } getEventData(list, pen) { const value = {}; if (list?.length) { list.forEach((item) => { const _pen = item.params ? this.findOne(item.params) : pen; for (let key in item.value) { if (item.value[key] === undefined || item.value[key] === '') { value[key] = _pen[key]; } else if (typeof item.value[key] === 'string' && item.value[key]?.indexOf('${') > -1) { let keys = item.value[key].match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys?.length) { value[key] = _pen[keys[0]] ?? this.getDynamicParam(keys[0]); } } else { value[key] = item.value[key]; } } }); } if (Object.keys(value).length) { return value; } else return {}; } message(options) { const message = new Message(this.canvas.parentElement, options); message.init(); } closeAll() { for (let key in messageList) { messageList[key].close(); } } async navigatorTo(id) { if (!id) { return; } // let href = window.location.href; // let arr: string[] = href.split('id='); // if (arr.length > 1) { // let idx = arr[1].indexOf('&'); // if (idx === -1) { // window.location.href = arr[0] + 'id=' + id; // } else { // window.location.href = arr[0] + 'id=' + id + arr[1].slice(idx); // } // } //路径参数更新 let hasId = queryURLParams()?.id; if (hasId) { const url = new URL(window.location); url.searchParams.set('id', id); history.pushState({}, '', url); } //图纸更新 const data = await getMeta2dData(this.store, id); if (data) { this.canvas.opening = true; this.open(data); this.lock(1); const width = this.store.data.width || this.store.options.width; const height = this.store.data.height || this.store.options.height; if (width && height) { this.fitSizeView(true, 0); } else { this.fitView(true, 10); } this.render(true); // document.title = data.name + "-" + window.name; } } doSendDataEvent(value, topics) { let data = JSON.stringify(value); if (this.mqttClient && this.mqttClient.connected) { if (topics) { topics.split(',').forEach((topic) => { this.mqttClient.publish(topic, data); }); } else { this.store.data.mqttTopics && this.store.data.mqttTopics.split(',').forEach((topic) => { this.mqttClient.publish(topic, data); }); } } if (this.websocket && this.websocket.readyState === 1) { this.websocket.send(data); } if (this.store.data.https || this.store.data.http) { this.sendDatabyHttp(data); } this.store.emitter.emit('sendData', data); } async sendDataToNetWork(value, pen, e) { const network = deepClone(e.network); if (network.data) { Object.assign(network, network.data); delete network.data; } if (network.protocol === 'iot') { if (this.store.data.iot.room) { let payload = { token: this.store.data.iot.token, data: value, }; if (value.name) { const keys = Object.keys(value).filter((key) => value[key] === 'aggregate'); if (keys.length) { const _value = deepClone(value); keys.forEach((key) => { delete _value[key]; }); delete _value.name; if (_value.start && typeof _value.start === 'string') { _value.start = Math.floor(new Date(_value.start).getTime() / 1000); } if (_value.end && typeof _value.end === 'string') { _value.end = Math.floor(new Date(_value.end).getTime() / 1000); } let payload = []; keys.forEach((key) => { let arr = key.split('#'); payload.push({ token: this.store.data.iot.token, deviceId: arr[0], key: arr[1], name: arr[1] + '_' + value.name, ..._value, }); }); this.iotMqttClient && this.iotMqttClient.publish(`le5le-iot/${this.store.data.iot.room}/property/aggregate`, JSON.stringify(payload)); return; } } this.iotMqttClient && this.iotMqttClient.publish(`le5le-iot/${this.store.data.iot.room}/properties/set`, JSON.stringify(payload)); } else { this.iotMqttClient && this.iotMqttClient.publish(`le5le-iot/property/set/${this.store.data.iot?.token}`, JSON.stringify(value)); } return; } if (!network.url) { return; } if (network.protocol === 'http') { if (typeof network.headers === 'object') { /*for (let i in network.headers) { if (typeof network.headers[i] === 'string') { let keys = network.headers[i].match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys) { network.headers[i] = network.headers[i].replace( `\${${keys[0]}}`, this.getDynamicParam(keys[0]) ); } } }*/ let headersStr = JSON.stringify(network.headers); let keys = headersStr.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys?.length) { for (let i = 0; i < keys.length; i++) { headersStr = headersStr.replace(`\${${keys[i]}}`, this.getDynamicParam(keys[i])); } } network.headers = JSON.parse(headersStr); } let params = undefined; let url = network.url; if (network.method === 'GET') { if (Object.keys(value).length !== 0) { if (url.includes('?')) { params = '&' + Object.keys(value) .map((key) => key + '=' + value[key]) .join('&'); } else { params = '?' + Object.keys(value) .map((key) => key + '=' + value[key]) .join('&'); } } } // if (network.method === 'POST') { if (url.indexOf('${') > -1) { let keys = url.match(/\$\{([^}]+)\}/g)?.map(m => m.slice(2, -1)); if (keys) { keys.forEach((key) => { url = url.replace(`\${${key}}`, getter(pen, key) || this.getDynamicParam(key)); }); } } // } const res = await fetch(url + (params ? params : ''), { headers: network.headers || {}, method: network.method, body: network.method === 'POST' ? JSON.stringify(value) : undefined, }); if (res.ok) { if (e.callback) { const data = await res.text(); if (!e.fn) { try { if (typeof e.callback !== 'string') { throw new Error('[meta2d] Function callback must be string'); } const fnJs = e.callback; e.fn = new Function('pen', 'data', 'context', fnJs); } catch (err) { console.error('[meta2d]: Error on make a function:', err, 'code:', e.callback); } } e.fn?.(pen, data, { meta2d: this, e }); } console.info('http消息发送成功'); } else { this.store.emitter.emit('error', { type: 'http', error: res }); } } else if (network.protocol === 'mqtt') { const clients = this.mqttClients?.filter((client) => client.options.href === network.url); if (clients && clients.length) { if (clients[0].connected) { network.topics.split(',').forEach((topic) => { clients[0].publish(topic, JSON.stringify(value)); }); } } else { //临时建立连接 let mqttClient = mqtt.connect(network.url, network.options); mqttClient.on('connect', () => { console.info('mqtt连接成功'); network.topics.split(',').forEach((topic) => { mqttClient.publish(topic, JSON.stringify(value)); setTimeout(() => { mqttClient?.end(); }, 1000); }); }); } } else if (network.protocol === 'websocket') { const websockets = this.websockets?.filter((socket) => socket.url === network.url); if (websockets && websockets.length) { if (websockets[0].readyState === 1) { websockets[0].send(JSON.stringify(value)); } } else { //临时建立连接 let websocket = new WebSocket(network.url, network.protocols || undefined); websocket.onopen = function () { console.info('websocket连接成功'); websocket.send(JSON.stringify(value)); setTimeout(() => { websocket.close(); }, 100); }; } } } resize(width, height) { this.canvas.resize(width, height); this.render(); this.store.emitter.emit('resize', { width, height }); if (this.canvas.scroll && this.canvas.scroll.isShow) { this.canvas.scroll.init(); } } /** * * @param emit 是否发送消息 */ async addPen(pen, history, emit = true, abs = false) { return await this.canvas.addPen(pen, history, emit, abs); } addPenSync(pen, history, emit = true, abs = false) { return this.canvas.addPenSync(pen, history, emit, abs); } async addPens(pens, history, abs = false) { return await this.canvas.addPens(pens, history, abs); } render(patchFlags) { this.canvas?.render(patchFlags); } async setBackgroundImage(url, data) { let that = this; async function loadImage(url) { return new Promise((resolve) => { const img = new Image(); img.src = url; if (that.store.options.cdn && !(url.startsWith('http') || url.startsWith('//') || url.startsWith('data:image'))) { img.src = that.store.options.cdn + url; } img.crossOrigin = that.store.options.crossOrigin || 'anonymous'; img.onload = () => { resolve(img); }; }); } this.store.data.bkImage = url; const width = data?.width || this.store.data?.width || this.store.options?.width; const height = data?.height || this.store.data?.height || this.store.options?.height; if (width && height) { this.canvas.canvasTemplate.canvas.style.backgroundImage = null; this.canvas && (this.canvas.canvasTemplate.bgPatchFlags = true); } else { this.canvas.canvasTemplate.canvas.style.backgroundImage = url ? `url('${url}')` : ''; } if (url) { const img = await loadImage(url); // 用作 toPng 的绘制 this.store.bkImg = img; if (width && height) { if (this.canvas) { this.canvas.canvasTemplate.init(); this.render(); } } } else { this.store.bkImg = null; } } setBackgroundColor(color = this.store.data.background) { this.store.data.background = color; // this.store.patchFlagsBackground = true; this.canvas && (this.canvas.canvasTemplate.bgPatchFlags = true); } setGrid({ grid = this.store.data.grid, gridColor = this.store.data.gridColor, gridSize = this.store.data.gridSize, gridRotate = this.store.data.gridRotate, } = {}) { this.store.data.grid = grid; this.store.data.gridColor = gridColor; this.store.data.gridSize = gridSize < 0 ? 0 : gridSize; this.store.data.gridRotate = gridRotate; // this.store.patchFlagsBackground = true; this.canvas && (this.canvas.canvasTemplate.bgPatchFlags = true); } setRule({ rule = this.store.data.rule, ruleColor = this.store.data.ruleColor, } = {}) { this.store.data.rule = rule; this.store.data.ruleColor = ruleColor; this.store.patchFlagsTop = true; } open(data, render = true) { this.clear(false, data?.template); this.canvas.autoPolylineFlag = true; if (data) { // 根据图纸的主题设置主题 if (data.theme) { this.setTheme(data.theme); } updateIframes(data.pens); this.setBackgroundImage(data.bkImage, data); Object.assign(this.store.data, data); this.store.data.pens = []; // 第一遍赋初值 for (const pen of data.pens) { if (!pen.id) { pen.id = s8(); } !pen.calculative && (pen.calculative = { canvas: this.canvas }); this.store.pens[pen.id] = pen; } for (const pen of data.pens) { this.canvas.makePen(pen); } //首次计算连线bug // for (const pen of data.pens) { // this.canvas.updateLines(pen); // } } this.canvas.patchFlagsLines.forEach((pen) => { if (pen.type) { this.canvas.initLineRect(pen); } }); if (!this.store.data.template) { this.store.data.template = s8(); } if (!render) { this.canvas.opening = true; } this.doInitJS(); this.initBindDatas(); this.initBinds(); this.doInitFn(); this.loadLineAnimateDraws(); this.initMessageEvents(); this.initGlobalTriggers(); this.startAnimate(); this.startVideo(); this.listenSocket(); this.connectSocket(); this.connectNetwork(); this.startDataMock(); this.canvas.initGlobalStyle(); this.render(); setTimeout(() => { const pen = this.store.data.pens.find((pen) => pen.autofocus); if (pen) { this.focus(pen.id); } }, 100); if (this.store.data.iconUrls) { for (const item of this.store.data.iconUrls) { loadCss(item, () => { this.render(); }); } } this.canvas.autoPolylineFlag = false; this.store.emitter.emit('opened'); if (this.canvas.scroll && this.canvas.scroll.isShow) { this.canvas.scroll.init(); } } dirtyData(active) { //获取画布脏数据 const pens = this.store.data.pens; const width = this.store.data.width || this.store.options.width; const height = this.store.data.height || this.store.options.height; const dirtyPens = []; for (let i = pens.length - 1; i >= 0; i--) { let pen = pens[i]; if (pen.parentId) { const parent = this.store.pens[pen.parentId]; if (pen.x > 10 || pen.y > 10 || pen.width > 10 || pen.height > 10) { // 子图元坐标值很大 dirtyPens.push(pen); } else if (!parent.children || !parent.children.includes(pen.id)) { //已经解组但子图元还有父图元id dirtyPens.push(pen); } } if (width && height) { //大屏区域外 let rect = this.getPenRect(pen); if (rect.x < -10 || rect.y < -10 || rect.x + rect.width > width || rect.y + rect.height > height) { dirtyPens.push(pen); } } //无效连线 单个锚点连线 if (pen.name === 'line') { if (pen.anchors.length < 2) { dirtyPens.push(pen); } } } if (!width || !height) { //2d 偏移量很大 let outpens = findOutliersByZScore(pens); outpens.forEach((item) => { let repeat = dirtyPens.filter((_item) => _item.id === item.id); if (!repeat.length) { dirtyPens.push(item); } }); } if (active) { this.active(dirtyPens); } return dirtyPens; } clearDirtyData() { let dirtyPens = this.dirtyData(); this.delete(dirtyPens, true); } cacheData(id) { if (id && this.store.options.cacheLength) { let index = this.store.cacheDatas.findIndex((item) => item.data && item.data._id === id); if (index === -1) { this.store.cacheDatas.push({ data: deepClone(this.store.data, true), // offscreen: new Array(2), // flag: new Array(2) }); if (this.store.cacheDatas.length > this.store.options.cacheLength) { this.store.cacheDatas.shift(); } } else { let cacheDatas = this.store.cacheDatas.splice(index, 1)[0]; this.store.cacheDatas.push(cacheDatas); } } } loadCacheData(id) { let index = this.store.cacheDatas.findIndex((item) => item.data && item.data._id === id); if (index === -1) { return; } // const ctx = this.canvas.canvas.getContext('2d'); // ctx.clearRect(0, 0, this.canvas.canvas.width, this.canvas.canvas.height); // for (let offs of this.store.cacheDatas[index].offscreen) { // if (offs) { // ctx.drawImage(offs, 0, 0, this.canvas.width, this.canvas.height); // } // } // ctx.clearRect(0, 0, this.canvas.canvas.width, this.canvas.canvas.height); this.store.data = this.store.cacheDatas[index].data; this.setBackgroundImage(this.store.data.bkImage); this.store.pens = {}; this.store.data.pens.forEach((pen) => { pen.calculative.canvas = this.canvas; this.store.pens[pen.id] = pen; globalStore.path2dDraws[pen.name] && this.store.path2dMap.set(pen, globalStore.path2dDraws[pen.name](pen)); pen.type && this.store.path2dMap.set(pen, globalStore.path2dDraws[pen.name](pen)); if (pen.image) { pen.calculative.imageDrawed = false; this.canvas.loadImage(pen); } }); this.render(); } loadLineAnimateDraws() { globalStore.lineAnimateDraws = {}; Object.entries(this.store.data.lineAnimateDraws).forEach(([key, drawFunc]) => { // @ts-ignore globalStore.lineAnimateDraws[key] = new Function('ctx', 'pen', 'state', 'index', drawFunc); }); } statistics() { const num = this.store.data.pens.length; const imgNum = this.store.data.pens.filter((pen) => pen.image).length; const imgDrawNum = this.store.data.pens.filter((pen) => pen.image && pen.calculative.inView).length; const domNum = this.store.data.pens.filter((pen) => pen.name.endsWith('Dom') || isDomShapes.includes(pen.name) || this.store.options.domShapes.includes(pen.name) || pen.externElement || pen.isDom).length; const aningNum = this.store.animates.size; let dataPointsNum = 0; Object.keys(this.store.bind).forEach((key) => { dataPointsNum += this.store.bind[key].length; }); Object.keys(this.store.bindDatas).forEach((key) => { dataPointsNum += this.store.bindDatas[key].length; }); return { "图元总数量": num, "图片图元数量": imgNum, "图片图元绘制数量": imgDrawNum, "dom图元数量": domNum, "正在执行的动画数量": aningNum, "数据点数量": dataPointsNum, }; } initBindDatas() { this.store.bindDatas = {}; this.store.data.pens.forEach((pen) => { pen.form?.forEach((formItem) => { let dataIds; if (formItem.dataIds) { if (Array.isArray(formItem.dataIds)) { dataIds = formItem.dataIds; } else { dataIds = [formItem.dataIds]; } } dataIds?.forEach((item) => { if (!this.store.bindDatas[item.dataId]) { this.store.bindDatas[item.dataId] = []; } this.store.bindDatas[item.dataId].push({ id: pen.id, formItem, }); }); }); }); } jetLinksList = []; jetLinksClient; initBinds() { this.jetLinksList = []; this.store.bind = {}; const devices = []; const properties = []; const computes = []; this.store.data.pens.forEach((pen) => { pen.realTimes?.forEach((realTime) => { if (realTime.bind && realTime.bind.id) { // if (!this.store.bind[realTime.bind.id]) { // this.store.bind[realTime.bind.id] = []; // } // this.store.bind[realTime.bind.id].push({ // id: pen.id, // key: realTime.key, // }); //JetLinks const Jet = this.store.data.networks?.some((item) => item.protocol === 'ADIIOT'); let productId = realTime.productId || pen.productId; let deviceId = realTime.deviceId || pen.deviceId; let propertyId = realTime.propertyId; let flag = false; if (Jet) { if (productId && typeof productId === 'string' && productId.indexOf('${') > -1) { let keys = productId.match(/(?<=\$\{).*?(?=\})/g); if (keys?.length) { productId = this.getDynamicParam(keys[0]) || productId; } flag = true; } if (deviceId && typeof deviceId === 'string' && deviceId.indexOf('${') > -1) { let keys = d