graphFrame.ts 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. import { GraphNode } from './graphNode';
  2. import { GraphCanvasComponent } 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. enum ResizingDirection {
  12. Right,
  13. Left,
  14. Top,
  15. Bottom,
  16. TopRight,
  17. TopLeft,
  18. BottomRight,
  19. BottomLeft,
  20. }
  21. export class GraphFrame {
  22. private readonly CollapsedWidth = 200;
  23. private static _FrameCounter = 0;
  24. private _name: string;
  25. private _color: Color3;
  26. private _x = 0;
  27. private _y = 0;
  28. private _gridAlignedX = 0;
  29. private _gridAlignedY = 0;
  30. private _width: number;
  31. private _height: number;
  32. public element: HTMLDivElement;
  33. private _borderElement: HTMLDivElement;
  34. private _headerElement: HTMLDivElement;
  35. private _headerTextElement: HTMLDivElement;
  36. private _headerCollapseElement: HTMLDivElement;
  37. private _headerCloseElement: HTMLDivElement;
  38. private _commentsElement: HTMLDivElement;
  39. private _portContainer: HTMLDivElement;
  40. private _outputPortContainer: HTMLDivElement;
  41. private _inputPortContainer: HTMLDivElement;
  42. private _nodes: GraphNode[] = [];
  43. private _ownerCanvas: GraphCanvasComponent;
  44. private _mouseStartPointX: Nullable<number> = null;
  45. private _mouseStartPointY: Nullable<number> = null;
  46. private _onSelectionChangedObserver: Nullable<Observer<Nullable<GraphNode | NodeLink | GraphFrame>>>;
  47. private _isCollapsed = false;
  48. private _ports: NodePort[] = [];
  49. private _controlledPorts: NodePort[] = [];
  50. private _id: number;
  51. private _comments: string;
  52. private _frameIsResizing: boolean;
  53. private _resizingDirection: Nullable<ResizingDirection>;
  54. private _minFrameHeight = 40;
  55. private _minFrameWidth = 220;
  56. private mouseXLimit: Nullable<number>;
  57. public onExpandStateChanged = new Observable<GraphFrame>();
  58. 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>`;
  59. 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>`;
  60. 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>`;
  61. public get id() {
  62. return this._id;
  63. }
  64. public get isCollapsed() {
  65. return this._isCollapsed;
  66. }
  67. private _createInputPort(port: NodePort, node: GraphNode) {
  68. let localPort = NodePort.CreatePortElement(port.connectionPoint, node, this._inputPortContainer, null, this._ownerCanvas.globalState)
  69. this._ports.push(localPort);
  70. port.delegatedPort = localPort;
  71. this._controlledPorts.push(port);
  72. }
  73. public set isCollapsed(value: boolean) {
  74. if (this._isCollapsed === value) {
  75. return;
  76. }
  77. this._isCollapsed = value;
  78. this._ownerCanvas._frameIsMoving = true;
  79. // Need to delegate the outside ports to the frame
  80. if (value) {
  81. this.element.classList.add("collapsed");
  82. this._moveFrame((this.width - this.CollapsedWidth) / 2, 0);
  83. for (var node of this._nodes) {
  84. node.isVisible = false;
  85. for (var port of node.outputPorts) { // Output
  86. if (port.connectionPoint.hasEndpoints) {
  87. let portAdded = false;
  88. for (var link of node.links) {
  89. if (link.portA === port && this.nodes.indexOf(link.nodeB!) === -1) {
  90. let localPort: NodePort;
  91. if (!portAdded) {
  92. portAdded = true;
  93. localPort = NodePort.CreatePortElement(port.connectionPoint, link.nodeB!, this._outputPortContainer, null, this._ownerCanvas.globalState);
  94. this._ports.push(localPort);
  95. } else {
  96. localPort = this._ports.filter(p => p.connectionPoint === port.connectionPoint)[0];
  97. }
  98. port.delegatedPort = localPort;
  99. this._controlledPorts.push(port);
  100. link.isVisible = true;
  101. }
  102. }
  103. } else {
  104. let localPort = NodePort.CreatePortElement(port.connectionPoint, node, this._outputPortContainer, null, this._ownerCanvas.globalState)
  105. this._ports.push(localPort);
  106. port.delegatedPort = localPort;
  107. this._controlledPorts.push(port);
  108. }
  109. }
  110. for (var port of node.inputPorts) { // Input
  111. if (port.connectionPoint.isConnected) {
  112. for (var link of node.links) {
  113. if (link.portB === port && this.nodes.indexOf(link.nodeA) === -1) {
  114. this._createInputPort(port, node);
  115. link.isVisible = true;
  116. }
  117. }
  118. } else {
  119. this._createInputPort(port, node);
  120. }
  121. }
  122. }
  123. } else {
  124. this.element.classList.remove("collapsed");
  125. this._outputPortContainer.innerHTML = "";
  126. this._inputPortContainer.innerHTML = "";
  127. this._ports.forEach(p => {
  128. p.dispose();
  129. });
  130. this._controlledPorts.forEach(port => {
  131. port.delegatedPort = null;
  132. port.refresh();
  133. })
  134. this._ports = [];
  135. this._controlledPorts = [];
  136. for (var node of this._nodes) {
  137. node.isVisible = true;
  138. }
  139. this._moveFrame(-(this.width - this.CollapsedWidth) / 2, 0);
  140. }
  141. this.cleanAccumulation();
  142. this._ownerCanvas._frameIsMoving = false;
  143. // UI
  144. if (this._isCollapsed) {
  145. this._headerCollapseElement.innerHTML = this.ExpandSVG;
  146. this._headerCollapseElement.title = "Expand";
  147. } else {
  148. this._headerCollapseElement.innerHTML = this.CollapseSVG;
  149. this._headerCollapseElement.title = "Collapse";
  150. }
  151. this.onExpandStateChanged.notifyObservers(this);
  152. }
  153. public get nodes() {
  154. return this._nodes;
  155. }
  156. public get name() {
  157. return this._name;
  158. }
  159. public set name(value: string) {
  160. this._name = value;
  161. this._headerTextElement.innerHTML = value;
  162. }
  163. public get color() {
  164. return this._color;
  165. }
  166. public set color(value: Color3) {
  167. this._color = value;
  168. this._headerElement.style.background = `rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, 1)`;
  169. this._headerElement.style.borderColor = `rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, 1)`;
  170. this.element.style.background = `rgba(${value.r * 255}, ${value.g * 255}, ${value.b * 255}, 0.7)`;
  171. }
  172. public get x() {
  173. return this._x;
  174. }
  175. public set x(value: number) {
  176. if (this._x === value) {
  177. return;
  178. }
  179. this._x = value;
  180. this._gridAlignedX = this._ownerCanvas.getGridPosition(value);
  181. this.element.style.left = `${this._gridAlignedX}px`;
  182. }
  183. public get y() {
  184. return this._y;
  185. }
  186. public set y(value: number) {
  187. if (this._y === value) {
  188. return;
  189. }
  190. this._y = value;
  191. this._gridAlignedY = this._ownerCanvas.getGridPosition(value);
  192. this.element.style.top = `${this._gridAlignedY}px`;
  193. }
  194. public get width() {
  195. return this._width;
  196. }
  197. public set width(value: number) {
  198. if (this._width === value) {
  199. return;
  200. }
  201. let viableWidth = value > this._minFrameWidth ? value : this._minFrameWidth;
  202. this._width = viableWidth;
  203. var gridAlignedRight = this._ownerCanvas.getGridPositionCeil(viableWidth + this._gridAlignedX);
  204. this.element.style.width = `${gridAlignedRight - this._gridAlignedX}px`;
  205. }
  206. public get height() {
  207. return this._height;
  208. }
  209. public set height(value: number) {
  210. if (this._height === value) {
  211. return;
  212. }
  213. this._height = value;
  214. var gridAlignedBottom = this._ownerCanvas.getGridPositionCeil(value + this._gridAlignedY);
  215. this.element.style.height = `${gridAlignedBottom - this._gridAlignedY}px`;
  216. }
  217. public get comments(): string {
  218. return this._comments;
  219. }
  220. public set comments(comments: string) {
  221. if (comments && !this._comments && comments.length > 0) {
  222. this.element.style.gridTemplateRows = "40px min-content 1fr";
  223. this._borderElement.style.gridRow = "1 / span 3";
  224. this._portContainer.style.gridRow = "3";
  225. this._commentsElement.classList.add("has-comments");
  226. } else if (!comments) {
  227. this.element.style.gridTemplateRows = "40px calc(100% - 40px)";
  228. this._borderElement.style.gridRow = "1 / span 2";
  229. this._portContainer.style.gridRow = "2";
  230. this._commentsElement.classList.remove('has-comments');
  231. }
  232. if (comments === "" || (comments && comments.length >= 0)) {
  233. this._commentsElement.innerText = comments;
  234. }
  235. this.height = this._borderElement.offsetHeight;
  236. this._comments = comments;
  237. this.updateMinHeightWithComments();
  238. }
  239. public constructor(candidate: Nullable<HTMLDivElement>, canvas: GraphCanvasComponent, doNotCaptureNodes = false) {
  240. this._id = GraphFrame._FrameCounter++;
  241. this._ownerCanvas = canvas;
  242. const root = canvas.frameContainer;
  243. this.element = root.ownerDocument!.createElement("div");
  244. this.element.classList.add("frame-box");
  245. root.appendChild(this.element);
  246. this._headerElement = root.ownerDocument!.createElement("div");
  247. this._headerElement.classList.add("frame-box-header");
  248. this._headerElement.addEventListener("dblclick", () => {
  249. this.isCollapsed = !this.isCollapsed;
  250. });
  251. this.element.appendChild(this._headerElement);
  252. this._borderElement = root.ownerDocument!.createElement("div");
  253. this._borderElement.classList.add("frame-box-border");
  254. this.element.appendChild(this._borderElement);
  255. // add resizing side handles
  256. const rightHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  257. rightHandle.className = "handle right-handle";
  258. this.element.appendChild(rightHandle);
  259. rightHandle.addEventListener("pointerdown", this._onRightHandlePointerDown);
  260. const leftHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  261. leftHandle.className = "handle left-handle";
  262. this.element.appendChild(leftHandle);
  263. leftHandle.addEventListener("pointerdown", this._onLeftHandlePointerDown);
  264. const bottomHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  265. bottomHandle.className = "handle bottom-handle";
  266. this.element.appendChild(bottomHandle);
  267. bottomHandle.addEventListener("pointerdown", this._onBottomHandlePointerDown);
  268. const topHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  269. topHandle.className = "handle top-handle";
  270. this.element.appendChild(topHandle);
  271. topHandle.addEventListener("pointerdown", this._onTopHandlePointerDown);
  272. const topRightCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  273. topRightCornerHandle.className = "handle right-handle top-right-corner-handle";
  274. this.element.appendChild(topRightCornerHandle);
  275. topRightCornerHandle.addEventListener("pointerdown", this._onTopRightHandlePointerDown);
  276. const bottomRightCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  277. bottomRightCornerHandle.className = "handle right-handle bottom-right-corner-handle";
  278. this.element.appendChild(bottomRightCornerHandle);
  279. bottomRightCornerHandle.addEventListener("pointerdown", this._onBottomRightHandlePointerDown);
  280. const topLeftCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  281. topLeftCornerHandle.className = "handle left-handle top-left-corner-handle";
  282. this.element.appendChild(topLeftCornerHandle);
  283. topLeftCornerHandle.addEventListener("pointerdown", this._onRightHandlePointerDown);
  284. const bottomLeftCornerHandle: HTMLDivElement = root.ownerDocument!.createElement("div");
  285. bottomLeftCornerHandle.className = "handle left-handle bottom-left-corner-handle";
  286. this.element.appendChild(bottomLeftCornerHandle);
  287. bottomLeftCornerHandle.addEventListener("pointerdown", this._onBottomLeftHandlePointerDown);
  288. // add header elements
  289. this._headerTextElement = root.ownerDocument!.createElement("div");
  290. this._headerTextElement.classList.add("frame-box-header-title");
  291. this._headerElement.appendChild(this._headerTextElement);
  292. this._headerCollapseElement = root.ownerDocument!.createElement("div");
  293. this._headerCollapseElement.classList.add("frame-box-header-collapse");
  294. this._headerCollapseElement.classList.add("frame-box-header-button");
  295. this._headerCollapseElement.title = "Collapse";
  296. this._headerCollapseElement.ondragstart= () => false;
  297. this._headerCollapseElement.addEventListener("pointerdown", (evt) => {
  298. this._headerCollapseElement.classList.add("down");
  299. evt.stopPropagation();
  300. });
  301. this._headerCollapseElement.addEventListener("pointerup", (evt) => {
  302. evt.stopPropagation();
  303. this._headerCollapseElement.classList.remove("down");
  304. this.isCollapsed = !this.isCollapsed;
  305. });
  306. this._headerCollapseElement.innerHTML = this.CollapseSVG;
  307. this._headerElement.appendChild(this._headerCollapseElement);
  308. this._headerCloseElement = root.ownerDocument!.createElement("div");
  309. this._headerCloseElement.classList.add("frame-box-header-close");
  310. this._headerCloseElement.classList.add("frame-box-header-button");
  311. this._headerCloseElement.title = "Close";
  312. this._headerCloseElement.ondragstart= () => false;
  313. this._headerCloseElement.addEventListener("pointerdown", (evt) => {
  314. evt.stopPropagation();
  315. });
  316. this._headerCloseElement.addEventListener("pointerup", (evt) => {
  317. evt.stopPropagation();
  318. this.dispose();
  319. });
  320. this._headerCloseElement.innerHTML = this.CloseSVG;
  321. this._headerElement.appendChild(this._headerCloseElement);
  322. this._portContainer = root.ownerDocument!.createElement("div");
  323. this._portContainer.classList.add("port-container");
  324. this.element.appendChild(this._portContainer);
  325. this._outputPortContainer = root.ownerDocument!.createElement("div");
  326. this._outputPortContainer.classList.add("outputsContainer");
  327. this._portContainer.appendChild(this._outputPortContainer);
  328. this._inputPortContainer = root.ownerDocument!.createElement("div");
  329. this._inputPortContainer.classList.add("inputsContainer");
  330. this._portContainer.appendChild(this._inputPortContainer);
  331. this.name = "Frame";
  332. this.color = Color3.FromInts(72, 72, 72);
  333. if (candidate) {
  334. this.x = parseFloat(candidate.style.left!.replace("px", ""));
  335. this.y = parseFloat(candidate.style.top!.replace("px", ""));
  336. this.width = parseFloat(candidate.style.width!.replace("px", ""));
  337. this.height = parseFloat(candidate.style.height!.replace("px", ""));
  338. this.cleanAccumulation();
  339. }
  340. this._headerTextElement.addEventListener("pointerdown", evt => this._onDown(evt));
  341. this._headerTextElement.addEventListener("pointerup", evt => this._onUp(evt));
  342. this._headerTextElement.addEventListener("pointermove", evt => this._onMove(evt));
  343. this._onSelectionChangedObserver = canvas.globalState.onSelectionChangedObservable.add(node => {
  344. if (node === this) {
  345. this.element.classList.add("selected");
  346. } else {
  347. this.element.classList.remove("selected");
  348. }
  349. });
  350. this._commentsElement = document.createElement('div');
  351. this._commentsElement.className = 'frame-comments';
  352. this._commentsElement.style.color = 'white';
  353. this._commentsElement.style.fontSize = '16px';
  354. this.element.appendChild(this._commentsElement);
  355. // Get nodes
  356. if (!doNotCaptureNodes) {
  357. this.refresh();
  358. }
  359. }
  360. public refresh() {
  361. this._nodes = [];
  362. this._ownerCanvas.globalState.onFrameCreated.notifyObservers(this);
  363. }
  364. public addNode(node: GraphNode) {
  365. let index = this.nodes.indexOf(node);
  366. if (index === -1) {
  367. this.nodes.push(node);
  368. }
  369. }
  370. public removeNode(node: GraphNode) {
  371. let index = this.nodes.indexOf(node);
  372. if (index > -1) {
  373. this.nodes.splice(index, 1);
  374. }
  375. }
  376. public syncNode(node: GraphNode) {
  377. if (this.isCollapsed) {
  378. return;
  379. }
  380. if (node.isOverlappingFrame(this)) {
  381. this.addNode(node);
  382. } else {
  383. this.removeNode(node);
  384. }
  385. }
  386. public cleanAccumulation() {
  387. for (var selectedNode of this._nodes) {
  388. selectedNode.cleanAccumulation();
  389. }
  390. this.x = this._ownerCanvas.getGridPosition(this.x);
  391. this.y = this._ownerCanvas.getGridPosition(this.y);
  392. }
  393. private _onDown(evt: PointerEvent) {
  394. evt.stopPropagation();
  395. this._mouseStartPointX = evt.clientX;
  396. this._mouseStartPointY = evt.clientY;
  397. this._headerTextElement.setPointerCapture(evt.pointerId);
  398. this._ownerCanvas.globalState.onSelectionChangedObservable.notifyObservers(this);
  399. this._ownerCanvas._frameIsMoving = true;
  400. this.move(this._ownerCanvas.getGridPosition(this.x), this._ownerCanvas.getGridPosition(this.y))
  401. }
  402. public move(newX: number, newY: number, align = true) {
  403. let oldX = this.x;
  404. let oldY = this.y;
  405. this.x = newX;
  406. this.y = newY;
  407. for (var selectedNode of this._nodes) {
  408. selectedNode.x += this.x - oldX;
  409. selectedNode.y += this.y - oldY;
  410. if (align) {
  411. selectedNode.cleanAccumulation(true);
  412. }
  413. }
  414. }
  415. private _onUp(evt: PointerEvent) {
  416. evt.stopPropagation();
  417. this.cleanAccumulation();
  418. this._mouseStartPointX = null;
  419. this._mouseStartPointY = null;
  420. this._headerTextElement.releasePointerCapture(evt.pointerId);
  421. this._ownerCanvas._frameIsMoving = false;
  422. }
  423. private _moveFrame(offsetX: number, offsetY: number) {
  424. this.x += offsetX;
  425. this.y += offsetY;
  426. for (var selectedNode of this._nodes) {
  427. selectedNode.x += offsetX;
  428. selectedNode.y += offsetY;
  429. }
  430. }
  431. private _onMove(evt: PointerEvent) {
  432. if (this._mouseStartPointX === null || this._mouseStartPointY === null || evt.ctrlKey || this._frameIsResizing) {
  433. return;
  434. }
  435. let newX = (evt.clientX - this._mouseStartPointX) / this._ownerCanvas.zoom;
  436. let newY = (evt.clientY - this._mouseStartPointY) / this._ownerCanvas.zoom;
  437. this._moveFrame(newX, newY);
  438. this._mouseStartPointX = evt.clientX;
  439. this._mouseStartPointY = evt.clientY;
  440. evt.stopPropagation();
  441. }
  442. private initResizing = (evt: PointerEvent) => {
  443. evt.stopPropagation();
  444. this._mouseStartPointX = evt.clientX;
  445. this._mouseStartPointY = evt.clientY;
  446. this._frameIsResizing = true;
  447. }
  448. private cleanUpResizing = (evt: PointerEvent) => {
  449. evt.stopPropagation();
  450. this._frameIsResizing = false;
  451. this._resizingDirection = null;
  452. this._mouseStartPointX = null;
  453. this._mouseStartPointY = null;
  454. this.mouseXLimit = null;
  455. this.refresh();
  456. }
  457. private updateMinHeightWithComments = () => {
  458. if (this.comments && this.comments.length > 0) {
  459. const minFrameHeightWithComments = this._commentsElement.offsetHeight + 40;
  460. this._minFrameHeight = minFrameHeightWithComments;
  461. }
  462. }
  463. private _onRightHandlePointerDown = (evt: PointerEvent) => {
  464. // tslint:disable-next-line: no-this-assignment
  465. const _this = this;
  466. this.initResizing(evt);
  467. _this._resizingDirection = ResizingDirection.Right;
  468. _this.mouseXLimit = evt.clientX - (_this.width - _this._minFrameWidth);
  469. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onRightHandlePointerUp);
  470. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onRightHandlePointerMove);
  471. }
  472. private _onRightHandlePointerMove = (evt: PointerEvent) => {
  473. const slack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  474. const xLimit = (this._mouseStartPointX as number) - slack;
  475. this._moveRightHandle(evt, xLimit);
  476. }
  477. private _moveRightHandle = (evt: PointerEvent, xLimit: number) => {
  478. // tslint:disable-next-line: no-this-assignment
  479. const _this = this;
  480. if (_this.mouseXLimit) {
  481. if (!_this._isResizingRight() || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientX < xLimit) {
  482. return;
  483. }
  484. if (_this._isResizingRight()) {
  485. evt.stopPropagation();
  486. const distanceMouseMoved = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  487. _this._expandRight(distanceMouseMoved, evt.clientX);
  488. _this._mouseStartPointX = evt.clientX;
  489. }
  490. }
  491. }
  492. private _onRightHandlePointerUp = (evt: PointerEvent) => {
  493. // tslint:disable-next-line: no-this-assignment
  494. const _this = this;
  495. if (_this._isResizingRight()) {
  496. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  497. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onRightHandlePointerUp);
  498. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onRightHandlePointerMove);
  499. _this.cleanUpResizing(evt);
  500. }
  501. }
  502. private _onBottomHandlePointerDown = (evt: PointerEvent) => {
  503. // tslint:disable-next-line: no-this-assignment
  504. const _this = this;
  505. _this.initResizing(evt);
  506. _this._resizingDirection = ResizingDirection.Bottom;
  507. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onBottomHandlePointerMove);
  508. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onBottomHandlePointerUp);
  509. }
  510. private _onBottomHandlePointerMove = (evt: PointerEvent) => {
  511. const slack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  512. const yLimit = (this._mouseStartPointY as number) - slack;
  513. this._moveBottomHandle(evt, yLimit);
  514. }
  515. private _moveBottomHandle = (evt: PointerEvent, yLimit: number) => {
  516. // tslint:disable-next-line: no-this-assignment
  517. const _this = this;
  518. if (_this._resizingDirection !== ResizingDirection.Bottom || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY < yLimit) {
  519. return;
  520. }
  521. if (_this._resizingDirection === ResizingDirection.Bottom) {
  522. evt.stopPropagation();
  523. const distanceMouseMoved = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  524. _this._expandBottom(distanceMouseMoved);
  525. _this._mouseStartPointY = evt.clientY;
  526. }
  527. }
  528. private _onBottomHandlePointerUp = (evt: PointerEvent) => {
  529. // tslint:disable-next-line: no-this-assignment
  530. const _this = this;
  531. if (_this._resizingDirection === ResizingDirection.Bottom) {
  532. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  533. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onBottomHandlePointerMove);
  534. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onBottomHandlePointerUp);
  535. _this.cleanUpResizing(evt);
  536. }
  537. }
  538. private _onLeftHandlePointerDown = (evt: PointerEvent) => {
  539. // tslint:disable-next-line: no-this-assignment
  540. const _this = this;
  541. _this.initResizing(evt);
  542. _this._resizingDirection = ResizingDirection.Left;
  543. _this.mouseXLimit = evt.clientX + _this.width - _this._minFrameWidth;
  544. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onLeftHandlePointerUp);
  545. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onLeftHandlePointerMove);
  546. }
  547. private _onLeftHandlePointerMove = (evt: PointerEvent) => {
  548. const slack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  549. const xLimit = (this._mouseStartPointX as number) + slack;
  550. this._moveLeftHandle(evt, xLimit);
  551. }
  552. private _moveLeftHandle = (evt: PointerEvent, xLimit: number) => {
  553. // tslint:disable-next-line: no-this-assignment
  554. const _this = this;
  555. if (_this.mouseXLimit) {
  556. if (_this._resizingDirection !== ResizingDirection.Left || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientX > xLimit) {
  557. return;
  558. }
  559. if (_this._resizingDirection === ResizingDirection.Left) {
  560. evt.stopPropagation();
  561. const distanceMouseMoved = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  562. _this._expandLeft(distanceMouseMoved);
  563. _this._mouseStartPointX = evt.clientX;
  564. }
  565. }
  566. }
  567. private _onLeftHandlePointerUp = (evt: PointerEvent) => {
  568. // tslint:disable-next-line: no-this-assignment
  569. const _this = this;
  570. if (_this._resizingDirection === ResizingDirection.Left) {
  571. _this.x = parseFloat(_this.element.style.left!.replace("px", ""));
  572. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  573. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onLeftHandlePointerUp);
  574. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onLeftHandlePointerMove);
  575. _this.cleanUpResizing(evt);
  576. }
  577. }
  578. private _onTopHandlePointerDown = (evt: PointerEvent) => {
  579. // tslint:disable-next-line: no-this-assignment
  580. const _this = this;
  581. _this.initResizing(evt);
  582. _this._resizingDirection = ResizingDirection.Top;
  583. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onTopHandlePointerUp);
  584. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onTopHandlePointerMove);
  585. }
  586. private _onTopHandlePointerMove = (evt: PointerEvent) => {
  587. const slack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  588. const yLimit = (this._mouseStartPointY as number) + slack;
  589. this._moveTopHandle(evt, yLimit);
  590. }
  591. private _moveTopHandle = (evt: PointerEvent, yLimit: number) => {
  592. // tslint:disable-next-line: no-this-assignment
  593. const _this = this;
  594. if (!_this._isResizingTop() || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY > yLimit) {
  595. return;
  596. }
  597. if (_this._isResizingTop()) {
  598. evt.stopPropagation();
  599. const distanceMouseMoved = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  600. _this._expandTop(distanceMouseMoved);
  601. _this._mouseStartPointY = evt.clientY;
  602. }
  603. }
  604. private _onTopHandlePointerUp = (evt: PointerEvent) => {
  605. // tslint:disable-next-line: no-this-assignment
  606. const _this = this;
  607. if (_this._isResizingTop()) {
  608. _this.y = parseFloat(_this.element.style.top!.replace("px", ""));
  609. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  610. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onTopHandlePointerUp);
  611. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onTopHandlePointerMove);
  612. _this.cleanUpResizing(evt);
  613. }
  614. }
  615. private _isResizingTop(){
  616. return this._resizingDirection === ResizingDirection.Top || this._resizingDirection === ResizingDirection.TopRight || this._resizingDirection === ResizingDirection.TopLeft;
  617. }
  618. private _isResizingRight(){
  619. return this._resizingDirection === ResizingDirection.Right || this._resizingDirection === ResizingDirection.TopRight || this._resizingDirection === ResizingDirection.BottomRight;
  620. }
  621. private _isResizingBottom(){
  622. return this._resizingDirection === ResizingDirection.Bottom || this._resizingDirection === ResizingDirection.BottomLeft || this._resizingDirection === ResizingDirection.BottomRight;
  623. }
  624. private _isResizingLeft(){
  625. return this._resizingDirection === ResizingDirection.Left || this._resizingDirection === ResizingDirection.TopLeft || this._resizingDirection === ResizingDirection.BottomLeft;
  626. }
  627. private _onTopRightHandlePointerDown = (evt: PointerEvent) => {
  628. // tslint:disable-next-line: no-this-assignment
  629. const _this = this;
  630. _this.initResizing(evt);
  631. // _this.initResizing(evt);
  632. _this._resizingDirection = ResizingDirection.TopRight;
  633. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onTopRightHandlePointerUp);
  634. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onTopRightHandlePointerMove);
  635. }
  636. private _onTopRightHandlePointerUp = (evt: PointerEvent) => {
  637. evt.stopPropagation();
  638. // tslint:disable-next-line: no-this-assignment
  639. const _this = this;
  640. if (_this._resizingDirection === ResizingDirection.TopRight) {
  641. _this.y = parseFloat(_this.element.style.top!.replace("px", ""));
  642. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  643. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  644. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onTopRightHandlePointerUp);
  645. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onTopRightHandlePointerMove);
  646. _this.cleanUpResizing(evt);
  647. }
  648. }
  649. private _onTopRightHandlePointerMove = (evt: PointerEvent) => {
  650. const topSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  651. const yLimit = (this._mouseStartPointY as number) + topSlack;
  652. const rightSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  653. const xLimit = (this._mouseStartPointX as number) - rightSlack;
  654. this._moveTopRightHandle(evt, xLimit, yLimit);
  655. }
  656. private _moveTopRightHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  657. // tslint:disable-next-line: no-this-assignment
  658. const _this = this;
  659. if (!(_this._isResizingTop() && _this._isResizingRight()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY > yLimit || evt.clientX < xLimit) {
  660. return;
  661. }
  662. if (_this._isResizingRight() && _this._isResizingTop()) {
  663. evt.stopPropagation();
  664. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  665. _this._expandRight(distanceMouseMovedX, evt.clientX);
  666. _this._mouseStartPointX = evt.clientX;
  667. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  668. _this._expandTop(distanceMouseMovedY);
  669. _this._mouseStartPointY = evt.clientY;
  670. }
  671. }
  672. private _onBottomRightHandlePointerDown = (evt: PointerEvent) => {
  673. // tslint:disable-next-line: no-this-assignment
  674. const _this = this;
  675. _this.initResizing(evt);
  676. // _this.initResizing(evt);
  677. _this._resizingDirection = ResizingDirection.BottomRight;
  678. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onBottomRightHandlePointerUp);
  679. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onBottomRightHandlePointerMove);
  680. }
  681. private _onBottomRightHandlePointerUp = (evt: PointerEvent) => {
  682. evt.stopPropagation();
  683. // tslint:disable-next-line: no-this-assignment
  684. const _this = this;
  685. if (_this._resizingDirection === ResizingDirection.BottomRight) {
  686. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  687. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  688. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onBottomRightHandlePointerUp);
  689. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onBottomRightHandlePointerMove);
  690. _this.cleanUpResizing(evt);
  691. }
  692. }
  693. private _onBottomRightHandlePointerMove = (evt: PointerEvent) => {
  694. const bottomSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  695. const yLimit = (this._mouseStartPointY as number) - bottomSlack;
  696. const rightSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  697. const xLimit = (this._mouseStartPointX as number) - rightSlack;
  698. this._moveBottomRightHandle(evt, xLimit, yLimit);
  699. }
  700. private _moveBottomRightHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  701. // tslint:disable-next-line: no-this-assignment
  702. const _this = this;
  703. if (!(_this._isResizingBottom() && _this._isResizingRight()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY < yLimit || evt.clientX < xLimit) {
  704. return;
  705. }
  706. if (_this._isResizingRight() && _this._isResizingBottom()) {
  707. evt.stopPropagation();
  708. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  709. _this._expandRight(distanceMouseMovedX, evt.clientX);
  710. _this._mouseStartPointX = evt.clientX;
  711. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  712. _this._expandBottom(distanceMouseMovedY);
  713. _this._mouseStartPointY = evt.clientY;
  714. }
  715. }
  716. private _onBottomLeftHandlePointerDown = (evt: PointerEvent) => {
  717. // tslint:disable-next-line: no-this-assignment
  718. const _this = this;
  719. _this.initResizing(evt);
  720. // _this.initResizing(evt);
  721. _this._resizingDirection = ResizingDirection.BottomLeft;
  722. _this.mouseXLimit = evt.clientX + _this.width - _this._minFrameWidth;
  723. _this._ownerCanvas.hostCanvas.addEventListener("pointerup", _this._onBottomLeftHandlePointerUp);
  724. _this._ownerCanvas.hostCanvas.addEventListener("pointermove", _this._onBottomLeftHandlePointerMove);
  725. }
  726. private _onBottomLeftHandlePointerUp = (evt: PointerEvent) => {
  727. evt.stopPropagation();
  728. // tslint:disable-next-line: no-this-assignment
  729. const _this = this;
  730. if (_this._resizingDirection === ResizingDirection.BottomLeft) {
  731. _this.height = parseFloat(_this.element.style.height.replace("px", ""));
  732. _this.x = parseFloat(_this.element.style.left!.replace("px", ""));
  733. _this.width = parseFloat(_this.element.style.width.replace("px", ""));
  734. _this._ownerCanvas.hostCanvas.removeEventListener("pointerup", _this._onBottomLeftHandlePointerUp);
  735. _this._ownerCanvas.hostCanvas.removeEventListener("pointermove", _this._onBottomLeftHandlePointerMove);
  736. _this.cleanUpResizing(evt);
  737. }
  738. }
  739. private _onBottomLeftHandlePointerMove = (evt: PointerEvent) => {
  740. const bottomSlack = (this.element.offsetHeight - this._minFrameHeight) * this._ownerCanvas.zoom;
  741. const yLimit = (this._mouseStartPointY as number) - bottomSlack;
  742. const leftSlack = (this.element.offsetWidth - this._minFrameWidth) * this._ownerCanvas.zoom;
  743. const xLimit = (this._mouseStartPointX as number) + leftSlack;
  744. this._moveBottomLeftHandle(evt, xLimit, yLimit);
  745. }
  746. private _moveBottomLeftHandle = (evt: PointerEvent, xLimit: number, yLimit: number) => {
  747. // tslint:disable-next-line: no-this-assignment
  748. const _this = this;
  749. if (!(_this._isResizingBottom() && _this._isResizingLeft()) || _this._mouseStartPointX === null || _this._mouseStartPointY === null || evt.clientY < yLimit || evt.clientX > xLimit) {
  750. return;
  751. }
  752. if (_this._isResizingLeft() && _this._isResizingBottom()) {
  753. evt.stopPropagation();
  754. const distanceMouseMovedX = (evt.clientX - _this._mouseStartPointX) / _this._ownerCanvas.zoom;
  755. _this._expandLeft(distanceMouseMovedX);
  756. _this._mouseStartPointX = evt.clientX;
  757. const distanceMouseMovedY = (evt.clientY - _this._mouseStartPointY) / _this._ownerCanvas.zoom;
  758. _this._expandBottom(distanceMouseMovedY);
  759. _this._mouseStartPointY = evt.clientY;
  760. }
  761. }
  762. private _expandLeft(widthModification: number) {
  763. const frameElementWidth = parseFloat(this.element.style.width.replace("px", ""));
  764. const frameElementLeft = parseFloat(this.element.style.left.replace("px", ""));
  765. this.element.style.width = `${frameElementWidth - widthModification}px`;
  766. this.element.style.left = `${frameElementLeft + widthModification}px`;
  767. this.updateMinHeightWithComments();
  768. // this.height = this._borderElement.offsetHeight;
  769. }
  770. private _expandTop(heightModification: number) {
  771. const frameElementHeight = parseFloat(this.element.style.height.replace("px", ""));
  772. const frameElementTop = parseFloat(this.element.style.top.replace("px", ""));
  773. this.element.style.height = `${frameElementHeight - heightModification}px`;
  774. this.element.style.top = `${frameElementTop + heightModification}px`;
  775. }
  776. private _expandRight(widthModification: number, x: number) {
  777. const frameElementWidth = parseFloat(this.element.style.width.replace("px", ""));
  778. if ((frameElementWidth + widthModification) > 20) {
  779. this._mouseStartPointX = x;
  780. this.element.style.width = `${frameElementWidth + widthModification}px`;
  781. }
  782. this.updateMinHeightWithComments();
  783. // this.height = this._borderElement.offsetHeight;
  784. }
  785. private _expandBottom(heightModification: number) {
  786. const frameElementHeight = parseFloat(this.element.style.height.replace("px", ""));
  787. this.element.style.height = `${frameElementHeight + heightModification}px`;
  788. }
  789. public dispose() {
  790. this.isCollapsed = false;
  791. if (this._onSelectionChangedObserver) {
  792. this._ownerCanvas.globalState.onSelectionChangedObservable.remove(this._onSelectionChangedObserver);
  793. }
  794. this.element.parentElement!.removeChild(this.element);
  795. this._ownerCanvas.frames.splice(this._ownerCanvas.frames.indexOf(this), 1);
  796. this.onExpandStateChanged.clear();
  797. }
  798. public serialize(): IFrameData {
  799. return {
  800. x: this._x,
  801. y: this._y,
  802. width: this._width,
  803. height: this._height,
  804. color: this._color.asArray(),
  805. name: this.name,
  806. isCollapsed: this.isCollapsed,
  807. blocks: this.nodes.map(n => n.block.uniqueId),
  808. comments: this._comments
  809. }
  810. }
  811. public export() {
  812. const state = this._ownerCanvas.globalState;
  813. const json = SerializationTools.Serialize(state.nodeMaterial, state, this.nodes.map(n => n.block));
  814. StringTools.DownloadAsFile(state.hostDocument, json, this._name + ".json");
  815. }
  816. public static Parse(serializationData: IFrameData, canvas: GraphCanvasComponent, map?: {[key: number]: number}) {
  817. let newFrame = new GraphFrame(null, canvas, true);
  818. const isCollapsed = !!serializationData.isCollapsed;
  819. newFrame.x = serializationData.x;
  820. newFrame.y = serializationData.y;
  821. newFrame.width = serializationData.width;
  822. newFrame.height = serializationData.height;
  823. newFrame.name = serializationData.name;
  824. newFrame.color = Color3.FromArray(serializationData.color);
  825. newFrame.comments = serializationData.comments;
  826. if (serializationData.blocks && map) {
  827. for (var blockId of serializationData.blocks) {
  828. let actualId = map[blockId];
  829. let node = canvas.nodes.filter(n => n.block.uniqueId === actualId);
  830. if (node.length) {
  831. newFrame.nodes.push(node[0]);
  832. }
  833. }
  834. } else {
  835. newFrame.refresh();
  836. }
  837. newFrame.isCollapsed = isCollapsed;
  838. if (isCollapsed) {
  839. canvas._frameIsMoving = true;
  840. newFrame._moveFrame(-(newFrame.width - newFrame.CollapsedWidth) / 2, 0);
  841. let diff = serializationData.x - newFrame.x;
  842. newFrame._moveFrame(diff, 0);
  843. newFrame.cleanAccumulation();
  844. for (var selectedNode of newFrame.nodes) {
  845. selectedNode.refresh();
  846. }
  847. canvas._frameIsMoving = false;
  848. }
  849. return newFrame;
  850. }
  851. }