graphFrame.ts 66 KB

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