graphFrame.ts 65 KB

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