graphFrame.ts 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. import { GraphNode } from './graphNode';
  2. import { GraphCanvasComponent, FramePortData } from './graphCanvas';
  3. import { Nullable } from 'babylonjs/types';
  4. import { Observer, Observable } from 'babylonjs/Misc/observable';
  5. import { NodeLink } from './nodeLink';
  6. import { IFrameData } from '../nodeLocationInfo';
  7. import { Color3 } from 'babylonjs/Maths/math.color';
  8. import { NodePort } from './nodePort';
  9. import { SerializationTools } from '../serializationTools';
  10. import { StringTools } from '../stringTools';
  11. import { FrameNodePort } from './frameNodePort';
  12. enum ResizingDirection {
  13. Right,
  14. Left,
  15. Top,
  16. Bottom,
  17. TopRight,
  18. TopLeft,
  19. BottomRight,
  20. BottomLeft
  21. }
  22. export enum FramePortPosition {
  23. Top, Middle, Bottom
  24. };
  25. export class GraphFrame {
  26. private readonly CollapsedWidth = 200;
  27. private static _FrameCounter = 0;
  28. private static _FramePortCounter = 0;
  29. private _name: string;
  30. private _color: Color3;
  31. private _x = 0;
  32. private _y = 0;
  33. private _gridAlignedX = 0;
  34. private _gridAlignedY = 0;
  35. private _width: number;
  36. private _height: number;
  37. public element: HTMLDivElement;
  38. private _borderElement: HTMLDivElement;
  39. private _headerElement: HTMLDivElement;
  40. private _headerTextElement: HTMLDivElement;
  41. private _headerCollapseElement: HTMLDivElement;
  42. private _headerCloseElement: HTMLDivElement;
  43. private _commentsElement: HTMLDivElement;
  44. private _portContainer: HTMLDivElement;
  45. private _outputPortContainer: HTMLDivElement;
  46. private _inputPortContainer: HTMLDivElement;
  47. private _nodes: GraphNode[] = [];
  48. private _ownerCanvas: GraphCanvasComponent;
  49. private _mouseStartPointX: Nullable<number> = null;
  50. private _mouseStartPointY: Nullable<number> = null;
  51. private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphFrame | GraphNode | NodeLink | NodePort | FramePortData>>>;
  52. private _onGraphNodeRemovalObserver: Nullable<Observer<GraphNode>>;
  53. private _onExposePortOnFrameObserver: Nullable<Observer<GraphNode>>;
  54. private _onNodeLinkDisposedObservers: Nullable<Observer<NodeLink>>[] = [];
  55. private _isCollapsed = false;
  56. private _frameInPorts: FrameNodePort[] = [];
  57. private _frameOutPorts: FrameNodePort[] = [];
  58. private _controlledPorts: NodePort[] = []; // Ports on Nodes that are shown on outside of frame
  59. private _id: number;
  60. private _comments: string;
  61. private _frameIsResizing: boolean;
  62. private _resizingDirection: Nullable<ResizingDirection>;
  63. private _minFrameHeight = 40;
  64. private _minFrameWidth = 220;
  65. private mouseXLimit: Nullable<number>;
  66. public onExpandStateChanged = new Observable<GraphFrame>();
  67. private readonly CloseSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g id="Layer_2" data-name="Layer 2"><path d="M16,15l5.85,5.84-1,1L15,15.93,9.15,21.78l-1-1L14,15,8.19,9.12l1-1L15,14l5.84-5.84,1,1Z"/></g></svg>`;
  68. private readonly ExpandSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g id="Layer_2" data-name="Layer 2"><path d="M22.31,7.69V22.31H7.69V7.69ZM21.19,8.81H8.81V21.19H21.19Zm-6.75,6.75H11.06V14.44h3.38V11.06h1.12v3.38h3.38v1.12H15.56v3.38H14.44Z"/></g></svg>`;
  69. private readonly CollapseSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><g id="Layer_2" data-name="Layer 2"><path d="M22.31,7.69V22.31H7.69V7.69ZM21.19,8.81H8.81V21.19H21.19Zm-2.25,6.75H11.06V14.44h7.88Z"/></g></svg>`;
  70. public get id() {
  71. return this._id;
  72. }
  73. public get isCollapsed() {
  74. return this._isCollapsed;
  75. }
  76. private _createInputPort(port: NodePort, node: GraphNode) {
  77. let localPort = FrameNodePort.CreateFrameNodePortElement(port.connectionPoint, node, this._inputPortContainer, null, this._ownerCanvas.globalState, true, GraphFrame._FramePortCounter++, this.id);
  78. this._frameInPorts.push(localPort);
  79. port.delegatedPort = localPort;
  80. this._controlledPorts.push(port);
  81. }
  82. // Mark ports with FramePortPosition for re-arrangement support
  83. private _markFramePortPositions() {
  84. // mark FrameInPorts
  85. if(this._frameInPorts.length == 2){
  86. this._frameInPorts[0].framePortPosition = FramePortPosition.Top;
  87. this._frameInPorts[1].framePortPosition = FramePortPosition.Bottom;
  88. } else {
  89. for(let i = 0; i < this._frameInPorts.length; i++) {
  90. const port = this._frameInPorts[i];
  91. if(i === 0){
  92. port.framePortPosition = FramePortPosition.Top;
  93. } else if(i === this._frameInPorts.length -1){
  94. port.framePortPosition = FramePortPosition.Bottom;
  95. } else {
  96. port.framePortPosition = FramePortPosition.Middle;
  97. }
  98. }
  99. }
  100. // mark FrameOutPorts
  101. if(this._frameOutPorts.length == 2){
  102. this._frameOutPorts[0].framePortPosition = FramePortPosition.Top;
  103. this._frameOutPorts[1].framePortPosition = FramePortPosition.Bottom;
  104. } else {
  105. for(let i = 0; i < this._frameOutPorts.length; i++) {
  106. const port = this._frameOutPorts[i];
  107. if(i === 0){
  108. port.framePortPosition = FramePortPosition.Top
  109. } else if(i === this._frameInPorts.length -1){
  110. port.framePortPosition = FramePortPosition.Bottom
  111. } else {
  112. port.framePortPosition = FramePortPosition.Middle
  113. }
  114. }
  115. }
  116. }
  117. private _createFramePorts() {
  118. for (var node of this._nodes) {
  119. node.isVisible = false;
  120. for (var port of node.outputPorts) { // Output
  121. if (port.connectionPoint.hasEndpoints) {
  122. let portAdded = false;
  123. for (var link of node.links) {
  124. if (link.portA === port && this.nodes.indexOf(link.nodeB!) === -1 || (link.portA === port && port.exposedOnFrame)) {
  125. let localPort: FrameNodePort;
  126. if (!portAdded) {
  127. portAdded = true;
  128. localPort = FrameNodePort.CreateFrameNodePortElement(port.connectionPoint, link.nodeA!, this._outputPortContainer, null, this._ownerCanvas.globalState, false, GraphFrame._FramePortCounter++, this.id);
  129. this._frameOutPorts.push(localPort);
  130. link.isVisible = true;
  131. const onLinkDisposedObserver = link.onDisposedObservable.add((nodeLink: NodeLink) => {
  132. this._redrawFramePorts();
  133. });
  134. this._onNodeLinkDisposedObservers.push(onLinkDisposedObserver);
  135. } else if (this.nodes.indexOf(link.nodeB!) === -1) {
  136. link.isVisible = true;
  137. localPort = this.ports.filter(p => p.connectionPoint === port.connectionPoint)[0];
  138. } else {
  139. localPort = this.ports.filter(p => p.connectionPoint === port.connectionPoint)[0];
  140. }
  141. port.delegatedPort = localPort;
  142. this._controlledPorts.push(port);
  143. }
  144. }
  145. } else if(port.exposedOnFrame) {
  146. let localPort = FrameNodePort.CreateFrameNodePortElement(port.connectionPoint, node, this._outputPortContainer, null, this._ownerCanvas.globalState, false, GraphFrame._FramePortCounter++, this.id);
  147. this._frameOutPorts.push(localPort);
  148. port.delegatedPort = localPort;
  149. this._controlledPorts.push(port);
  150. }
  151. }
  152. for (var port of node.inputPorts) { // Input
  153. if (port.connectionPoint.isConnected) {
  154. for (var link of node.links) {
  155. if (link.portB === port && this.nodes.indexOf(link.nodeA) === -1) {
  156. this._createInputPort(port, node);
  157. link.isVisible = true;
  158. const onLinkDisposedObserver = link.onDisposedObservable.add((nodeLink: NodeLink) => {
  159. this._redrawFramePorts();
  160. });
  161. this._onNodeLinkDisposedObservers.push(onLinkDisposedObserver);
  162. }
  163. }
  164. } else if(port.exposedOnFrame) {
  165. this._createInputPort(port, node);
  166. }
  167. }
  168. }
  169. }
  170. private _redrawFramePorts() {
  171. if(!this.isCollapsed) {
  172. return;
  173. }
  174. this._outputPortContainer.innerHTML = "";
  175. this._inputPortContainer.innerHTML = "";
  176. this.ports.forEach((framePort:FrameNodePort) => {
  177. framePort.dispose();
  178. });
  179. this._controlledPorts.forEach(port => {
  180. port.delegatedPort = null;
  181. port.refresh();
  182. })
  183. this._frameInPorts = [];
  184. this._frameOutPorts = [];
  185. this._controlledPorts = [];
  186. this._createFramePorts();
  187. this.ports.forEach((framePort: FrameNodePort) => framePort.node._refreshLinks());
  188. }
  189. public set isCollapsed(value: boolean) {
  190. if (this._isCollapsed === value) {
  191. return;
  192. }
  193. this._isCollapsed = value;
  194. this._ownerCanvas._frameIsMoving = true;
  195. // Need to delegate the outside ports to the frame
  196. if (value) {
  197. this.element.classList.add("collapsed");
  198. this._moveFrame((this.width - this.CollapsedWidth) / 2, 0);
  199. this._createFramePorts()
  200. this._markFramePortPositions()
  201. } else {
  202. this.element.classList.remove("collapsed");
  203. this._outputPortContainer.innerHTML = "";
  204. this._inputPortContainer.innerHTML = "";
  205. this._frameInPorts.forEach(p => {
  206. p.dispose();
  207. });
  208. this._frameOutPorts.forEach(p => {
  209. p.dispose();
  210. });
  211. this._controlledPorts.forEach(port => {
  212. port.delegatedPort = null;
  213. port.refresh();
  214. })
  215. this._frameInPorts = [];
  216. this._frameOutPorts = [];
  217. this._controlledPorts = [];
  218. this._onNodeLinkDisposedObservers = [];
  219. for (var node of this._nodes) {
  220. node.isVisible = true;
  221. }
  222. this._moveFrame(-(this.width - this.CollapsedWidth) / 2, 0);
  223. }
  224. this.cleanAccumulation();
  225. this._ownerCanvas._frameIsMoving = false;
  226. // UI
  227. if (this._isCollapsed) {
  228. this._headerCollapseElement.innerHTML = this.ExpandSVG;
  229. this._headerCollapseElement.title = "Expand";
  230. } else {
  231. this._headerCollapseElement.innerHTML = this.CollapseSVG;
  232. this._headerCollapseElement.title = "Collapse";
  233. }
  234. this.onExpandStateChanged.notifyObservers(this);
  235. }
  236. public get nodes() {
  237. return this._nodes;
  238. }
  239. public get ports(){
  240. return this._frameInPorts.concat(this._frameOutPorts);
  241. }
  242. public get name() {
  243. return this._name;
  244. }
  245. public set name(value: string) {
  246. this._name = value;
  247. this._headerTextElement.innerHTML = value;
  248. }
  249. public get color() {
  250. return this._color;
  251. }
  252. public set color(value: Color3) {
  253. this._color = value;
  254. this._headerElement.style.background = `rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, 1)`;
  255. this._headerElement.style.borderColor = `rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, 1)`;
  256. this.element.style.background = `rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, 0.7)`;
  257. }
  258. public get x() {
  259. return this._x;
  260. }
  261. public set x(value: number) {
  262. if (this._x === value) {
  263. return;
  264. }
  265. this._x = value;
  266. this._gridAlignedX = this._ownerCanvas.getGridPosition(value);
  267. this.element.style.left = `${this._gridAlignedX}px`;
  268. }
  269. public get y() {
  270. return this._y;
  271. }
  272. public set y(value: number) {
  273. if (this._y === value) {
  274. return;
  275. }
  276. this._y = value;
  277. this._gridAlignedY = this._ownerCanvas.getGridPosition(value);
  278. this.element.style.top = `${this._gridAlignedY}px`;
  279. }
  280. public get width() {
  281. return this._width;
  282. }
  283. public set width(value: number) {
  284. if (this._width === value) {
  285. return;
  286. }
  287. let viableWidth = value > this._minFrameWidth ? value : this._minFrameWidth;
  288. this._width = viableWidth;
  289. var gridAlignedRight = this._ownerCanvas.getGridPositionCeil(viableWidth + this._gridAlignedX);
  290. this.element.style.width = `${gridAlignedRight - this._gridAlignedX}px`;
  291. }
  292. public get height() {
  293. return this._height;
  294. }
  295. public set height(value: number) {
  296. if (this._height === value) {
  297. return;
  298. }
  299. this._height = value;
  300. var gridAlignedBottom = this._ownerCanvas.getGridPositionCeil(value + this._gridAlignedY);
  301. this.element.style.height = `${gridAlignedBottom - this._gridAlignedY}px`;
  302. }
  303. public get comments(): string {
  304. return this._comments;
  305. }
  306. public set comments(comments: string) {
  307. if (comments && !this._comments && comments.length > 0) {
  308. this.element.style.gridTemplateRows = "40px min-content 1fr";
  309. this._borderElement.style.gridRow = "1 / span 3";
  310. this._portContainer.style.gridRow = "3";
  311. this._commentsElement.classList.add("has-comments");
  312. } else if (!comments) {
  313. this.element.style.gridTemplateRows = "40px calc(100% - 40px)";
  314. this._borderElement.style.gridRow = "1 / span 2";
  315. this._portContainer.style.gridRow = "2";
  316. this._commentsElement.classList.remove('has-comments');
  317. }
  318. if (comments === "" || (comments && comments.length >= 0)) {
  319. (this._commentsElement.children[0] as HTMLSpanElement).innerText = comments;
  320. }
  321. this.height = this._borderElement.offsetHeight;
  322. this._comments = comments;
  323. this.updateMinHeightWithComments();
  324. }
  325. public constructor(candidate: Nullable<HTMLDivElement>, canvas: GraphCanvasComponent, doNotCaptureNodes = false) {
  326. this._id = GraphFrame._FrameCounter++;
  327. this._ownerCanvas = canvas;
  328. const root = canvas.frameContainer;
  329. this.element = root.ownerDocument!.createElement("div");
  330. this.element.classList.add("frame-box");
  331. root.appendChild(this.element);
  332. this._headerElement = root.ownerDocument!.createElement("div");
  333. this._headerElement.classList.add("frame-box-header");
  334. this._headerElement.addEventListener("dblclick", () => {
  335. this.isCollapsed = !this.isCollapsed;
  336. });
  337. this.element.appendChild(this._headerElement);
  338. this._borderElement = root.ownerDocument!.createElement("div");
  339. this._borderElement.classList.add("frame-box-border");
  340. this.element.appendChild(this._borderElement);
  341. // add resizing side handles
  342. const rightHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  343. rightHandle.className = "handle right-handle";
  344. this.element.appendChild(rightHandle);
  345. rightHandle.addEventListener("pointerdown", this._onRightHandlePointerDown);
  346. const leftHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  347. leftHandle.className = "handle left-handle";
  348. this.element.appendChild(leftHandle);
  349. leftHandle.addEventListener("pointerdown", this._onLeftHandlePointerDown);
  350. const bottomHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  351. bottomHandle.className = "handle bottom-handle";
  352. this.element.appendChild(bottomHandle);
  353. bottomHandle.addEventListener("pointerdown", this._onBottomHandlePointerDown);
  354. const topHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  355. topHandle.className = "handle top-handle";
  356. this.element.appendChild(topHandle);
  357. topHandle.addEventListener("pointerdown", this._onTopHandlePointerDown);
  358. const topRightCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  359. topRightCornerHandle.className = "handle right-handle top-right-corner-handle";
  360. this.element.appendChild(topRightCornerHandle);
  361. topRightCornerHandle.addEventListener("pointerdown", this._onTopRightHandlePointerDown);
  362. const bottomRightCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  363. bottomRightCornerHandle.className = "handle right-handle bottom-right-corner-handle";
  364. this.element.appendChild(bottomRightCornerHandle);
  365. bottomRightCornerHandle.addEventListener("pointerdown", this._onBottomRightHandlePointerDown);
  366. const topLeftCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  367. topLeftCornerHandle.className = "handle left-handle top-left-corner-handle";
  368. this.element.appendChild(topLeftCornerHandle);
  369. topLeftCornerHandle.addEventListener("pointerdown", this._onTopLeftHandlePointerDown);
  370. const bottomLeftCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  371. bottomLeftCornerHandle.className = "handle left-handle bottom-left-corner-handle";
  372. this.element.appendChild(bottomLeftCornerHandle);
  373. bottomLeftCornerHandle.addEventListener("pointerdown", this._onBottomLeftHandlePointerDown);
  374. // add header elements
  375. this._headerTextElement = root.ownerDocument!.createElement("div");
  376. this._headerTextElement.classList.add("frame-box-header-title");
  377. this._headerElement.appendChild(this._headerTextElement);
  378. this._headerCollapseElement = root.ownerDocument!.createElement("div");
  379. this._headerCollapseElement.classList.add("frame-box-header-collapse");
  380. this._headerCollapseElement.classList.add("frame-box-header-button");
  381. this._headerCollapseElement.title = "Collapse";
  382. this._headerCollapseElement.ondragstart= () => false;
  383. this._headerCollapseElement.addEventListener("pointerdown", (evt) => {
  384. this._headerCollapseElement.classList.add("down");
  385. evt.stopPropagation();
  386. });
  387. this._headerCollapseElement.addEventListener("pointerup", (evt) => {
  388. evt.stopPropagation();
  389. this._headerCollapseElement.classList.remove("down");
  390. this.isCollapsed = !this.isCollapsed;
  391. });
  392. this._headerCollapseElement.innerHTML = this.CollapseSVG;
  393. this._headerElement.appendChild(this._headerCollapseElement);
  394. this._headerCloseElement = root.ownerDocument!.createElement("div");
  395. this._headerCloseElement.classList.add("frame-box-header-close");
  396. this._headerCloseElement.classList.add("frame-box-header-button");
  397. this._headerCloseElement.title = "Close";
  398. this._headerCloseElement.ondragstart= () => false;
  399. this._headerCloseElement.addEventListener("pointerdown", (evt) => {
  400. evt.stopPropagation();
  401. });
  402. this._headerCloseElement.addEventListener("pointerup", (evt) => {
  403. evt.stopPropagation();
  404. this.dispose();
  405. });
  406. this._headerCloseElement.innerHTML = this.CloseSVG;
  407. this._headerElement.appendChild(this._headerCloseElement);
  408. this._portContainer = root.ownerDocument!.createElement("div");
  409. this._portContainer.classList.add("port-container");
  410. this.element.appendChild(this._portContainer);
  411. this._outputPortContainer = root.ownerDocument!.createElement("div");
  412. this._outputPortContainer.classList.add("outputsContainer");
  413. this._portContainer.appendChild(this._outputPortContainer);
  414. this._inputPortContainer = root.ownerDocument!.createElement("div");
  415. this._inputPortContainer.classList.add("inputsContainer");
  416. this._portContainer.appendChild(this._inputPortContainer);
  417. this.name = "Frame";
  418. this.color = Color3.FromInts(72, 72, 72);
  419. if (candidate) {
  420. this.x = parseFloat(candidate.style.left!.replace("px", ""));
  421. this.y = parseFloat(candidate.style.top!.replace("px", ""));
  422. this.width = parseFloat(candidate.style.width!.replace("px", ""));
  423. this.height = parseFloat(candidate.style.height!.replace("px", ""));
  424. this.cleanAccumulation();
  425. }
  426. this._headerTextElement.addEventListener("pointerdown", evt => this._onDown(evt));
  427. this._headerTextElement.addEventListener("pointerup", evt => this._onUp(evt));
  428. this._headerTextElement.addEventListener("pointermove", evt => this._onMove(evt));
  429. this._onSelectionChangedObserver = canvas.globalState.onSelectionChangedObservable.add(node => {
  430. if (node === this) {
  431. this.element.classList.add("selected");
  432. } else {
  433. this.element.classList.remove("selected");
  434. }
  435. });
  436. this._onGraphNodeRemovalObserver = canvas.globalState.onGraphNodeRemovalObservable.add((node: GraphNode) => {
  437. // remove node from this._nodes
  438. const index = this._nodes.indexOf(node);
  439. if (index === -1) {
  440. return;
  441. } else {
  442. node.enclosingFrameId = -1;
  443. this._nodes.splice(index, 1);
  444. }
  445. });
  446. this._onExposePortOnFrameObserver = canvas.globalState.onExposePortOnFrameObservable.add((node: GraphNode) => {
  447. if (this.nodes.indexOf(node) === -1) {
  448. return;
  449. }
  450. this._redrawFramePorts();
  451. });
  452. this._commentsElement = document.createElement('div');
  453. this._commentsElement.className = 'frame-comments';
  454. this._commentsElement.style.color = 'white';
  455. this._commentsElement.style.fontSize = '16px';
  456. let commentSpan = document.createElement('span');
  457. commentSpan.className = "frame-comment-span"
  458. this._commentsElement.appendChild(commentSpan)
  459. this.element.appendChild(this._commentsElement);
  460. // Get nodes
  461. if (!doNotCaptureNodes) {
  462. this.refresh();
  463. }
  464. }
  465. public refresh() {
  466. this._nodes = [];
  467. this._ownerCanvas.globalState.onFrameCreatedObservable.notifyObservers(this);
  468. }
  469. public addNode(node: GraphNode) {
  470. let index = this.nodes.indexOf(node);
  471. if (index === -1) {
  472. this.nodes.push(node);
  473. }
  474. }
  475. public removeNode(node: GraphNode) {
  476. let index = this.nodes.indexOf(node);
  477. if (index > -1) {
  478. node.enclosingFrameId = -1;
  479. this.nodes.splice(index, 1);
  480. }
  481. }
  482. public syncNode(node: GraphNode) {
  483. if (this.isCollapsed) {
  484. return;
  485. }
  486. if (node.isOverlappingFrame(this)) {
  487. this.addNode(node);
  488. } else {
  489. this.removeNode(node);
  490. }
  491. }
  492. public cleanAccumulation() {
  493. for (var selectedNode of this._nodes) {
  494. selectedNode.cleanAccumulation();
  495. }
  496. this.x = this._ownerCanvas.getGridPosition(this.x);
  497. this.y = this._ownerCanvas.getGridPosition(this.y);
  498. }
  499. private _onDown(evt: PointerEvent) {
  500. evt.stopPropagation();
  501. this._mouseStartPointX = evt.clientX;
  502. this._mouseStartPointY = evt.clientY;
  503. this._headerTextElement.setPointerCapture(evt.pointerId);
  504. this._ownerCanvas.globalState.onSelectionChangedObservable.notifyObservers(this);
  505. this._ownerCanvas._frameIsMoving = true;
  506. this.move(this._ownerCanvas.getGridPosition(this.x), this._ownerCanvas.getGridPosition(this.y))
  507. }
  508. public move(newX: number, newY: number, align = true) {
  509. let oldX = this.x;
  510. let oldY = this.y;
  511. this.x = newX;
  512. this.y = newY;
  513. for (var selectedNode of this._nodes) {
  514. selectedNode.x += this.x - oldX;
  515. selectedNode.y += this.y - oldY;
  516. if (align) {
  517. selectedNode.cleanAccumulation(true);
  518. }
  519. }
  520. }
  521. private _onUp(evt: PointerEvent) {
  522. evt.stopPropagation();
  523. this.cleanAccumulation();
  524. this._mouseStartPointX = null;
  525. this._mouseStartPointY = null;
  526. this._headerTextElement.releasePointerCapture(evt.pointerId);
  527. this._ownerCanvas._frameIsMoving = false;
  528. }
  529. private _moveFrame(offsetX: number, offsetY: number) {
  530. this.x += offsetX;
  531. this.y += offsetY;
  532. for (var selectedNode of this._nodes) {
  533. selectedNode.x += offsetX;
  534. selectedNode.y += offsetY;
  535. }
  536. }
  537. private _onMove(evt: PointerEvent) {
  538. if (this._mouseStartPointX === null || this._mouseStartPointY === null || evt.ctrlKey || this._frameIsResizing) {
  539. return;
  540. }
  541. let newX = (evt.clientX - this._mouseStartPointX) / this._ownerCanvas.zoom;
  542. let newY = (evt.clientY - this._mouseStartPointY) / this._ownerCanvas.zoom;
  543. this._moveFrame(newX, newY);
  544. this._mouseStartPointX = evt.clientX;
  545. this._mouseStartPointY = evt.clientY;
  546. evt.stopPropagation();
  547. }
  548. public moveFramePortUp(nodePort: FrameNodePort){
  549. let elementsArray: ChildNode[];
  550. if(nodePort.isInput) {
  551. if(this._inputPortContainer.children.length < 2) {
  552. return;
  553. }
  554. elementsArray = Array.from(this._inputPortContainer.childNodes);
  555. this._movePortUp(elementsArray, nodePort, this._frameInPorts);
  556. } else {
  557. if(this._outputPortContainer.children.length < 2) {
  558. return;
  559. }
  560. elementsArray = Array.from(this._outputPortContainer.childNodes);
  561. this._movePortUp(elementsArray, nodePort, this._frameOutPorts);
  562. }
  563. this.ports.forEach((framePort: FrameNodePort) => framePort.node._refreshLinks());
  564. }
  565. private _movePortUp(elementsArray: ChildNode[], nodePort: FrameNodePort, framePortList: FrameNodePort[]) {
  566. // update UI
  567. const indexInElementArray = (elementsArray as HTMLElement[]).findIndex(elem => elem.dataset.framePortId === `${nodePort.framePortId}`)
  568. if(indexInElementArray === 0){
  569. return;
  570. }
  571. const secondPortElement = elementsArray[indexInElementArray];
  572. const firstPortElement = elementsArray[indexInElementArray -1];
  573. firstPortElement.parentElement?.insertBefore(secondPortElement, firstPortElement);
  574. // update Frame Port Container
  575. const indexInContainer = framePortList.findIndex(framePort => framePort === nodePort);
  576. [framePortList[indexInContainer -1], framePortList[indexInContainer]] = [framePortList[indexInContainer], framePortList[indexInContainer -1]]; // swap idicies
  577. //special case framePortList.length == 2
  578. if(framePortList.length == 2) {
  579. framePortList[1].framePortPosition = FramePortPosition.Bottom;
  580. framePortList[0].framePortPosition = FramePortPosition.Top;
  581. } else {
  582. // notify nodePort if it is now at Top (indexInElementArray === 1)
  583. if (indexInElementArray === 1) {
  584. framePortList[1].framePortPosition = FramePortPosition.Middle;
  585. framePortList[0].framePortPosition = FramePortPosition.Top;
  586. } else if(indexInContainer === elementsArray.length-1) {
  587. framePortList[framePortList.length -1].framePortPosition = FramePortPosition.Bottom;
  588. framePortList[framePortList.length -2].framePortPosition = FramePortPosition.Middle;
  589. } else {
  590. nodePort.framePortPosition = FramePortPosition.Middle;
  591. }
  592. }
  593. }
  594. public moveFramePortDown(nodePort: FrameNodePort){
  595. let elementsArray: ChildNode[];
  596. if(nodePort.isInput) {
  597. if(this._inputPortContainer.children.length < 2) {
  598. return;
  599. }
  600. elementsArray = Array.from(this._inputPortContainer.childNodes);
  601. this._movePortDown(elementsArray, nodePort, this._frameInPorts);
  602. } else {
  603. if(this._outputPortContainer.children.length < 2) {
  604. return;
  605. }
  606. elementsArray = Array.from(this._outputPortContainer.childNodes);
  607. this._movePortDown(elementsArray, nodePort, this._frameOutPorts);
  608. }
  609. this.ports.forEach((framePort: FrameNodePort) => framePort.node._refreshLinks());
  610. }
  611. private _movePortDown(elementsArray: ChildNode[], nodePort: FrameNodePort, framePortList: FrameNodePort[]) {
  612. // update UI
  613. const indexInElementArray = (elementsArray as HTMLElement[]).findIndex(elem => elem.dataset.framePortId === `${nodePort.framePortId}`)
  614. if(indexInElementArray === elementsArray.length -1){
  615. return;
  616. }
  617. const firstPort = elementsArray[indexInElementArray];
  618. const secondPort = elementsArray[indexInElementArray + 1];
  619. firstPort.parentElement?.insertBefore(secondPort, firstPort);
  620. // update Frame Port Container
  621. const indexInContainer = framePortList.findIndex(framePort => framePort === nodePort);
  622. [framePortList[indexInContainer], framePortList[indexInContainer + 1]] = [framePortList[indexInContainer + 1], framePortList[indexInContainer]]; // swap idicies
  623. // notify nodePort if it is now at bottom (indexInContainer === elementsArray.length-2)
  624. if(framePortList.length == 2) {
  625. framePortList[0].framePortPosition = FramePortPosition.Top;
  626. framePortList[1].framePortPosition = FramePortPosition.Bottom;
  627. } else {
  628. if(indexInContainer === elementsArray.length-2) {
  629. framePortList[elementsArray.length-2].framePortPosition = FramePortPosition.Middle;
  630. framePortList[elementsArray.length-1].framePortPosition = FramePortPosition.Bottom;
  631. } else if(indexInContainer === 0){
  632. framePortList[0].framePortPosition = FramePortPosition.Top;
  633. framePortList[1].framePortPosition = FramePortPosition.Middle;
  634. } else {
  635. nodePort.framePortPosition = FramePortPosition.Middle;
  636. }
  637. }
  638. }
  639. private initResizing = (evt: PointerEvent) => {
  640. evt.stopPropagation();
  641. this._mouseStartPointX = evt.clientX;
  642. this._mouseStartPointY = evt.clientY;
  643. this._frameIsResizing = true;
  644. }
  645. private cleanUpResizing = (evt: PointerEvent) => {
  646. evt.stopPropagation();
  647. this._frameIsResizing = false;
  648. this._resizingDirection = null;
  649. this._mouseStartPointX = null;
  650. this._mouseStartPointY = null;
  651. this.mouseXLimit = null;
  652. this.refresh();
  653. }
  654. private updateMinHeightWithComments = () => {
  655. if (this.comments && this.comments.length > 0) {
  656. const minFrameHeightWithComments = this._commentsElement.offsetHeight + 40;
  657. this._minFrameHeight = minFrameHeightWithComments;
  658. }
  659. }
  660. private _isResizingTop(){
  661. return this._resizingDirection === ResizingDirection.Top || this._resizingDirection === ResizingDirection.TopRight || this._resizingDirection === ResizingDirection.TopLeft;
  662. }
  663. private _isResizingRight(){
  664. return this._resizingDirection === ResizingDirection.Right || this._resizingDirection === ResizingDirection.TopRight || this._resizingDirection === ResizingDirection.BottomRight;
  665. }
  666. private _isResizingBottom(){
  667. return this._resizingDirection === ResizingDirection.Bottom || this._resizingDirection === ResizingDirection.BottomLeft || this._resizingDirection === ResizingDirection.BottomRight;
  668. }
  669. private _isResizingLeft() {
  670. return this._resizingDirection === ResizingDirection.Left || this._resizingDirection === ResizingDirection.TopLeft || this._resizingDirection === ResizingDirection.BottomLeft;
  671. }
  672. private _onRightHandlePointerDown = (evt: PointerEvent) => {
  673. // tslint:disable-next-line: no-this-assignment
  674. const _this = this;
  675. if (_this.isCollapsed) {
  676. return;
  677. }
  678. this.initResizing(evt);
  679. _this._resizingDirection = ResizingDirection.Right;
  680. _this.mouseXLimit = evt.clientX - (_this.width - _this._minFrameWidth);
  681. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onRightHandlePointerUp);
  682. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onRightHandlePointerMove);
  683. }
  684. private _onRightHandlePointerMove = (evt: PointerEvent) => {
  685. const slack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  686. const xLimit = (this._mouseStartPointX as number) - slack;
  687. this._moveRightHandle(evt, xLimit);
  688. }
  689. private _moveRightHandle = (evt: PointerEvent, xLimit: number) => {
  690. // tslint:disable-next-line: no-this-assignment
  691. const _this = this;
  692. if (_this.mouseXLimit) {
  693. if (!_this._isResizingRight() || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientX < xLimit) {
  694. return;
  695. }
  696. if (_this._isResizingRight()) {
  697. evt.stopPropagation();
  698. const distanceMouseMoved = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  699. _this._expandRight(distanceMouseMoved, evt.clientX);
  700. _this._mouseStartPointX = evt.clientX;
  701. }
  702. }
  703. }
  704. private _onRightHandlePointerUp = (evt: PointerEvent) => {
  705. // tslint:disable-next-line: no-this-assignment
  706. const _this = this;
  707. if (_this._isResizingRight()) {
  708. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  709. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onRightHandlePointerUp);
  710. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onRightHandlePointerMove);
  711. _this.cleanUpResizing(evt);
  712. }
  713. }
  714. private _onBottomHandlePointerDown = (evt: PointerEvent) => {
  715. // tslint:disable-next-line: no-this-assignment
  716. const _this = this;
  717. if (_this.isCollapsed) {
  718. return;
  719. }
  720. _this.initResizing(evt);
  721. _this._resizingDirection = ResizingDirection.Bottom;
  722. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onBottomHandlePointerMove);
  723. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onBottomHandlePointerUp);
  724. }
  725. private _onBottomHandlePointerMove = (evt: PointerEvent) => {
  726. const slack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  727. const yLimit = (this._mouseStartPointY as number) - slack;
  728. this._moveBottomHandle(evt, yLimit);
  729. }
  730. private _moveBottomHandle = (evt: PointerEvent, yLimit: number) => {
  731. // tslint:disable-next-line: no-this-assignment
  732. const _this = this;
  733. if (_this._resizingDirection !== ResizingDirection.Bottom || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY < yLimit) {
  734. return;
  735. }
  736. if (_this._resizingDirection === ResizingDirection.Bottom) {
  737. evt.stopPropagation();
  738. const distanceMouseMoved = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  739. _this._expandBottom(distanceMouseMoved);
  740. _this._mouseStartPointY = evt.clientY;
  741. }
  742. }
  743. private _onBottomHandlePointerUp = (evt: PointerEvent) => {
  744. // tslint:disable-next-line: no-this-assignment
  745. const _this = this;
  746. if (_this._resizingDirection === ResizingDirection.Bottom) {
  747. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  748. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onBottomHandlePointerMove);
  749. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onBottomHandlePointerUp);
  750. _this.cleanUpResizing(evt);
  751. }
  752. }
  753. private _onLeftHandlePointerDown = (evt: PointerEvent) => {
  754. // tslint:disable-next-line: no-this-assignment
  755. const _this = this;
  756. if (_this.isCollapsed) {
  757. return;
  758. }
  759. _this.initResizing(evt);
  760. _this._resizingDirection = ResizingDirection.Left;
  761. _this.mouseXLimit = evt.clientX + _this.width - _this._minFrameWidth;
  762. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onLeftHandlePointerUp);
  763. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onLeftHandlePointerMove);
  764. }
  765. private _onLeftHandlePointerMove = (evt: PointerEvent) => {
  766. const slack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  767. const xLimit = (this._mouseStartPointX as number) + slack;
  768. this._moveLeftHandle(evt, xLimit);
  769. }
  770. private _moveLeftHandle = (evt: PointerEvent, xLimit: number) => {
  771. // tslint:disable-next-line: no-this-assignment
  772. const _this = this;
  773. if (_this.mouseXLimit) {
  774. if (_this._resizingDirection !== ResizingDirection.Left || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientX > xLimit) {
  775. return;
  776. }
  777. if (_this._resizingDirection === ResizingDirection.Left) {
  778. evt.stopPropagation();
  779. const distanceMouseMoved = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  780. _this._expandLeft(distanceMouseMoved);
  781. _this._mouseStartPointX = evt.clientX;
  782. }
  783. }
  784. }
  785. private _onLeftHandlePointerUp = (evt: PointerEvent) => {
  786. // tslint:disable-next-line: no-this-assignment
  787. const _this = this;
  788. if (_this._resizingDirection === ResizingDirection.Left) {
  789. _this.x = parseFloat(_this.element.style.left!.replace("px", ""));
  790. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  791. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onLeftHandlePointerUp);
  792. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onLeftHandlePointerMove);
  793. _this.cleanUpResizing(evt);
  794. }
  795. }
  796. private _onTopHandlePointerDown = (evt: PointerEvent) => {
  797. // tslint:disable-next-line: no-this-assignment
  798. const _this = this;
  799. if (_this.isCollapsed) {
  800. return;
  801. }
  802. _this.initResizing(evt);
  803. _this._resizingDirection = ResizingDirection.Top;
  804. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onTopHandlePointerUp);
  805. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onTopHandlePointerMove);
  806. }
  807. private _onTopHandlePointerMove = (evt: PointerEvent) => {
  808. const slack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  809. const yLimit = (this._mouseStartPointY as number) + slack;
  810. this._moveTopHandle(evt, yLimit);
  811. }
  812. private _moveTopHandle = (evt: PointerEvent, yLimit: number) => {
  813. // tslint:disable-next-line: no-this-assignment
  814. const _this = this;
  815. if (!_this._isResizingTop() || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY > yLimit) {
  816. return;
  817. }
  818. if (_this._isResizingTop()) {
  819. evt.stopPropagation();
  820. const distanceMouseMoved = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  821. _this._expandTop(distanceMouseMoved);
  822. _this._mouseStartPointY = evt.clientY;
  823. }
  824. }
  825. private _onTopHandlePointerUp = (evt: PointerEvent) => {
  826. // tslint:disable-next-line: no-this-assignment
  827. const _this = this;
  828. if (_this._isResizingTop()) {
  829. _this.y = parseFloat(_this.element.style.top!.replace("px", ""));
  830. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  831. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onTopHandlePointerUp);
  832. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onTopHandlePointerMove);
  833. _this.cleanUpResizing(evt);
  834. }
  835. }
  836. private _onTopRightHandlePointerDown = (evt: PointerEvent) => {
  837. // tslint:disable-next-line: no-this-assignment
  838. const _this = this;
  839. if (_this.isCollapsed) {
  840. return;
  841. }
  842. _this.initResizing(evt);
  843. _this._resizingDirection = ResizingDirection.TopRight;
  844. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onTopRightHandlePointerUp);
  845. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onTopRightHandlePointerMove);
  846. }
  847. private _onTopRightHandlePointerMove = (evt: PointerEvent) => {
  848. const topSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  849. const yLimit = (this._mouseStartPointY as number) + topSlack;
  850. const rightSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  851. const xLimit = (this._mouseStartPointX as number) - rightSlack;
  852. this._moveTopRightHandle(evt, xLimit, yLimit);
  853. }
  854. private _moveTopRightHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  855. // tslint:disable-next-line: no-this-assignment
  856. const _this = this;
  857. if (!(_this._isResizingTop() && _this._isResizingRight()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null) {
  858. return;
  859. }
  860. if (_this._isResizingRight() && _this._isResizingTop()) {
  861. evt.stopPropagation();
  862. if (evt.clientY < yLimit && evt.clientX > xLimit) { // able to move in X and Y
  863. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  864. _this._expandRight(distanceMouseMovedX, evt.clientX);
  865. _this._mouseStartPointX = evt.clientX;
  866. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  867. _this._expandTop(distanceMouseMovedY);
  868. _this._mouseStartPointY = evt.clientY;
  869. } else if (evt.clientY > yLimit && evt.clientX > xLimit) { // able to move in X but not Y
  870. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  871. _this._expandRight(distanceMouseMovedX, evt.clientX);
  872. _this._mouseStartPointX = evt.clientX;
  873. } else if (evt.clientY < yLimit && evt.clientX < xLimit) { // able to move in Y but not X
  874. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  875. _this._expandTop(distanceMouseMovedY);
  876. _this._mouseStartPointY = evt.clientY;
  877. }
  878. }
  879. }
  880. private _onTopRightHandlePointerUp = (evt: PointerEvent) => {
  881. evt.stopPropagation();
  882. // tslint:disable-next-line: no-this-assignment
  883. const _this = this;
  884. if (_this._resizingDirection === ResizingDirection.TopRight) {
  885. _this.y = parseFloat(_this.element.style.top!.replace("px", ""));
  886. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  887. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  888. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onTopRightHandlePointerUp);
  889. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onTopRightHandlePointerMove);
  890. _this.cleanUpResizing(evt);
  891. }
  892. }
  893. private _onBottomRightHandlePointerDown = (evt: PointerEvent) => {
  894. // tslint:disable-next-line: no-this-assignment
  895. const _this = this;
  896. if (_this.isCollapsed) {
  897. return;
  898. }
  899. _this.initResizing(evt);
  900. _this._resizingDirection = ResizingDirection.BottomRight;
  901. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onBottomRightHandlePointerUp);
  902. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onBottomRightHandlePointerMove);
  903. }
  904. private _onBottomRightHandlePointerMove = (evt: PointerEvent) => {
  905. const bottomSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  906. const yLimit = (this._mouseStartPointY as number) - bottomSlack;
  907. const rightSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  908. const xLimit = (this._mouseStartPointX as number) - rightSlack;
  909. this._moveBottomRightHandle(evt, xLimit, yLimit);
  910. }
  911. private _moveBottomRightHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  912. // tslint:disable-next-line: no-this-assignment
  913. const _this = this;
  914. if (!(_this._isResizingBottom() && _this._isResizingRight()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null) {
  915. return;
  916. }
  917. if (_this._isResizingRight() && _this._isResizingBottom()) {
  918. evt.stopPropagation();
  919. if (evt.clientY > yLimit && evt.clientX > xLimit) { // able to move in X and Y
  920. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  921. _this._expandRight(distanceMouseMovedX, evt.clientX);
  922. _this._mouseStartPointX = evt.clientX;
  923. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  924. _this._expandBottom(distanceMouseMovedY);
  925. _this._mouseStartPointY = evt.clientY;
  926. } else if (evt.clientY < yLimit && evt.clientX > xLimit) { // able to move in X but not Y
  927. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  928. _this._expandRight(distanceMouseMovedX, evt.clientX);
  929. _this._mouseStartPointX = evt.clientX;
  930. } else if (evt.clientY > yLimit && evt.clientX < xLimit) { // able to move in Y but not X
  931. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  932. _this._expandBottom(distanceMouseMovedY);
  933. _this._mouseStartPointY = evt.clientY;
  934. }
  935. }
  936. }
  937. private _onBottomRightHandlePointerUp = (evt: PointerEvent) => {
  938. evt.stopPropagation();
  939. // tslint:disable-next-line: no-this-assignment
  940. const _this = this;
  941. if (_this._resizingDirection === ResizingDirection.BottomRight) {
  942. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  943. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  944. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onBottomRightHandlePointerUp);
  945. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onBottomRightHandlePointerMove);
  946. _this.cleanUpResizing(evt);
  947. }
  948. }
  949. private _onBottomLeftHandlePointerDown = (evt: PointerEvent) => {
  950. // tslint:disable-next-line: no-this-assignment
  951. const _this = this;
  952. if (_this.isCollapsed) {
  953. return;
  954. }
  955. _this.initResizing(evt);
  956. _this._resizingDirection = ResizingDirection.BottomLeft;
  957. _this.mouseXLimit = evt.clientX + _this.width - _this._minFrameWidth;
  958. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onBottomLeftHandlePointerUp);
  959. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onBottomLeftHandlePointerMove);
  960. }
  961. private _onBottomLeftHandlePointerMove = (evt: PointerEvent) => {
  962. const bottomSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  963. const yLimit = (this._mouseStartPointY as number) - bottomSlack;
  964. const leftSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  965. const xLimit = (this._mouseStartPointX as number) + leftSlack;
  966. this._moveBottomLeftHandle(evt, xLimit, yLimit);
  967. }
  968. private _moveBottomLeftHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  969. // tslint:disable-next-line: no-this-assignment
  970. const _this = this;
  971. if (!(_this._isResizingBottom() && _this._isResizingLeft()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null) {
  972. return;
  973. }
  974. if (_this._isResizingLeft() && _this._isResizingBottom()) {
  975. evt.stopPropagation();
  976. if (evt.clientY > yLimit && evt.clientX < xLimit) { // able to move in X and Y
  977. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  978. _this._expandLeft(distanceMouseMovedX);
  979. _this._mouseStartPointX = evt.clientX;
  980. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  981. _this._expandBottom(distanceMouseMovedY);
  982. _this._mouseStartPointY = evt.clientY;
  983. } else if (evt.clientY < yLimit && evt.clientX < xLimit) { // able to move in X but not Y
  984. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  985. _this._expandLeft(distanceMouseMovedX);
  986. _this._mouseStartPointX = evt.clientX;
  987. } else if (evt.clientY > yLimit && evt.clientX > xLimit) { // able to move in Y but not X
  988. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  989. _this._expandBottom(distanceMouseMovedY);
  990. _this._mouseStartPointY = evt.clientY;
  991. }
  992. }
  993. }
  994. private _onBottomLeftHandlePointerUp = (evt: PointerEvent) => {
  995. evt.stopPropagation();
  996. // tslint:disable-next-line: no-this-assignment
  997. const _this = this;
  998. if (_this._resizingDirection === ResizingDirection.BottomLeft) {
  999. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  1000. _this.x = parseFloat(_this.element.style.left!.replace("px", ""));
  1001. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  1002. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onBottomLeftHandlePointerUp);
  1003. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onBottomLeftHandlePointerMove);
  1004. _this.cleanUpResizing(evt);
  1005. }
  1006. }
  1007. private _onTopLeftHandlePointerDown = (evt: PointerEvent) => {
  1008. // tslint:disable-next-line: no-this-assignment
  1009. const _this = this;
  1010. if (_this.isCollapsed) {
  1011. return;
  1012. }
  1013. _this.initResizing(evt);
  1014. _this._resizingDirection = ResizingDirection.TopLeft;
  1015. _this.mouseXLimit = evt.clientX + _this.width - _this._minFrameWidth;
  1016. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onTopLeftHandlePointerUp);
  1017. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onTopLeftHandlePointerMove);
  1018. }
  1019. private _onTopLeftHandlePointerMove = (evt: PointerEvent) => {
  1020. const topSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  1021. const yLimit = (this._mouseStartPointY as number) + topSlack;
  1022. const leftSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  1023. const xLimit = (this._mouseStartPointX as number) + leftSlack;
  1024. this._moveTopLeftHandle(evt, xLimit, yLimit);
  1025. }
  1026. private _moveTopLeftHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  1027. // tslint:disable-next-line: no-this-assignment
  1028. const _this = this;
  1029. if (!(_this._isResizingTop() && _this._isResizingLeft()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null) {
  1030. return;
  1031. }
  1032. if (_this._isResizingLeft() && _this._isResizingTop()) {
  1033. evt.stopPropagation();
  1034. if (evt.clientY < yLimit && evt.clientX < xLimit) { // able to move in X and Y
  1035. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  1036. _this._expandLeft(distanceMouseMovedX);
  1037. _this._mouseStartPointX = evt.clientX;
  1038. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  1039. _this._expandTop(distanceMouseMovedY);
  1040. _this._mouseStartPointY = evt.clientY;
  1041. } else if (evt.clientY > yLimit && evt.clientX < xLimit) { // able to move in X but not Y
  1042. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  1043. _this._expandLeft(distanceMouseMovedX);
  1044. _this._mouseStartPointX = evt.clientX;
  1045. } else if (evt.clientY < yLimit && evt.clientX > xLimit) { // able to move in Y but not X
  1046. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  1047. _this._expandTop(distanceMouseMovedY);
  1048. _this._mouseStartPointY = evt.clientY;
  1049. }
  1050. }
  1051. }
  1052. private _onTopLeftHandlePointerUp = (evt: PointerEvent) => {
  1053. evt.stopPropagation();
  1054. // tslint:disable-next-line: no-this-assignment
  1055. const _this = this;
  1056. if (_this._resizingDirection === ResizingDirection.TopLeft) {
  1057. _this.y = parseFloat(_this.element.style.top!.replace("px", ""));
  1058. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  1059. _this.x = parseFloat(_this.element.style.left!.replace("px", ""));
  1060. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  1061. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onTopLeftHandlePointerUp);
  1062. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onTopLeftHandlePointerMove);
  1063. _this.cleanUpResizing(evt);
  1064. }
  1065. }
  1066. private _expandLeft(widthModification: number) {
  1067. const frameElementWidth = parseFloat(this.element.style.width.replace("px", ""));
  1068. const frameElementLeft = parseFloat(this.element.style.left.replace("px", ""));
  1069. this.element.style.width = `${frameElementWidth - widthModification}px`;
  1070. this.element.style.left = `${frameElementLeft + widthModification}px`;
  1071. this.updateMinHeightWithComments();
  1072. }
  1073. private _expandTop(heightModification: number) {
  1074. const frameElementHeight = parseFloat(this.element.style.height.replace("px", ""));
  1075. const frameElementTop = parseFloat(this.element.style.top.replace("px", ""));
  1076. this.element.style.height = `${frameElementHeight - heightModification}px`;
  1077. this.element.style.top = `${frameElementTop + heightModification}px`;
  1078. }
  1079. private _expandRight(widthModification: number, x: number) {
  1080. const frameElementWidth = parseFloat(this.element.style.width.replace("px", ""));
  1081. if ((frameElementWidth + widthModification) > 20) {
  1082. this._mouseStartPointX = x;
  1083. this.element.style.width = `${frameElementWidth + widthModification}px`;
  1084. }
  1085. this.updateMinHeightWithComments();
  1086. }
  1087. private _expandBottom(heightModification: number) {
  1088. const frameElementHeight = parseFloat(this.element.style.height.replace("px", ""));
  1089. this.element.style.height = `${frameElementHeight + heightModification}px`;
  1090. }
  1091. public dispose() {
  1092. //this.isCollapsed = false;
  1093. this._nodes.forEach(node => {
  1094. node.enclosingFrameId = -1;
  1095. });
  1096. if (this._onSelectionChangedObserver) {
  1097. this._ownerCanvas.globalState.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
  1098. };
  1099. if(this._onGraphNodeRemovalObserver) {
  1100. this._ownerCanvas.globalState.onGraphNodeRemovalObservable.remove(this._onGraphNodeRemovalObserver);
  1101. };
  1102. if(this._onExposePortOnFrameObserver) {
  1103. this._ownerCanvas.globalState.onExposePortOnFrameObservable.remove(this._onExposePortOnFrameObserver);
  1104. };
  1105. this.element.parentElement?.removeChild(this.element);
  1106. this._ownerCanvas.frames.splice(this._ownerCanvas.frames.indexOf(this), 1);
  1107. this.onExpandStateChanged.clear();
  1108. }
  1109. public serialize(): IFrameData {
  1110. return {
  1111. x: this._x,
  1112. y: this._y,
  1113. width: this._width,
  1114. height: this._height,
  1115. color: this._color.asArray(),
  1116. name: this.name,
  1117. isCollapsed: true, //keeping closed to make reimporting cleaner
  1118. blocks: this.nodes.map(n => n.block.uniqueId),
  1119. comments: this._comments
  1120. }
  1121. }
  1122. public export() {
  1123. const state = this._ownerCanvas.globalState;
  1124. const json = SerializationTools.Serialize(state.nodeMaterial, state, this);
  1125. StringTools.DownloadAsFile(state.hostDocument, json, this._name + ".json");
  1126. }
  1127. public static Parse(serializationData: IFrameData, canvas: GraphCanvasComponent, map?: {[key: number]: number}) {
  1128. let newFrame = new GraphFrame(null, canvas, true);
  1129. const isCollapsed = !!serializationData.isCollapsed;
  1130. newFrame.x = serializationData.x;
  1131. newFrame.y = serializationData.y;
  1132. newFrame.width = serializationData.width;
  1133. newFrame.height = serializationData.height;
  1134. newFrame.name = serializationData.name;
  1135. newFrame.color = Color3.FromArray(serializationData.color);
  1136. newFrame.comments = serializationData.comments;
  1137. if (serializationData.blocks && map) {
  1138. for (var blockId of serializationData.blocks) {
  1139. let actualId = map[blockId];
  1140. let node = canvas.nodes.filter(n => n.block.uniqueId === actualId);
  1141. if (node.length) {
  1142. newFrame.nodes.push(node[0]);
  1143. node[0].enclosingFrameId = newFrame.id;
  1144. }
  1145. }
  1146. } else {
  1147. newFrame.refresh();
  1148. }
  1149. newFrame.isCollapsed = isCollapsed;
  1150. if (isCollapsed) {
  1151. canvas._frameIsMoving = true;
  1152. newFrame._moveFrame(-(newFrame.width - newFrame.CollapsedWidth) / 2, 0);
  1153. let diff = serializationData.x - newFrame.x;
  1154. newFrame._moveFrame(diff, 0);
  1155. newFrame.cleanAccumulation();
  1156. for (var selectedNode of newFrame.nodes) {
  1157. selectedNode.refresh();
  1158. }
  1159. canvas._frameIsMoving = false;
  1160. }
  1161. return newFrame;
  1162. }
  1163. }