graphFrame.ts 66 KB

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