NodeEditor.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. import { Styles, Canvas, CircleMenu, ButtonInput, StringInput, ContextMenu, Tips, Search, Loader, Node, TreeViewNode, TreeViewInput, Element } from '../libs/flow.module.js';
  2. import { BasicMaterialEditor } from './materials/BasicMaterialEditor.js';
  3. import { StandardMaterialEditor } from './materials/StandardMaterialEditor.js';
  4. import { PointsMaterialEditor } from './materials/PointsMaterialEditor.js';
  5. import { OperatorEditor } from './math/OperatorEditor.js';
  6. import { NormalizeEditor } from './math/NormalizeEditor.js';
  7. import { InvertEditor } from './math/InvertEditor.js';
  8. import { LimiterEditor } from './math/LimiterEditor.js';
  9. import { DotEditor } from './math/DotEditor.js';
  10. import { PowerEditor } from './math/PowerEditor.js';
  11. import { AngleEditor } from './math/AngleEditor.js';
  12. import { TrigonometryEditor } from './math/TrigonometryEditor.js';
  13. import { FloatEditor } from './inputs/FloatEditor.js';
  14. import { Vector2Editor } from './inputs/Vector2Editor.js';
  15. import { Vector3Editor } from './inputs/Vector3Editor.js';
  16. import { Vector4Editor } from './inputs/Vector4Editor.js';
  17. import { SliderEditor } from './inputs/SliderEditor.js';
  18. import { ColorEditor } from './inputs/ColorEditor.js';
  19. import { TextureEditor } from './inputs/TextureEditor.js';
  20. import { BlendEditor } from './display/BlendEditor.js';
  21. import { NormalMapEditor } from './display/NormalMapEditor.js';
  22. import { UVEditor } from './accessors/UVEditor.js';
  23. import { MatcapUVEditor } from './accessors/MatcapUVEditor.js';
  24. import { PositionEditor } from './accessors/PositionEditor.js';
  25. import { NormalEditor } from './accessors/NormalEditor.js';
  26. import { PreviewEditor } from './utils/PreviewEditor.js';
  27. import { TimerEditor } from './utils/TimerEditor.js';
  28. import { OscillatorEditor } from './utils/OscillatorEditor.js';
  29. import { SplitEditor } from './utils/SplitEditor.js';
  30. import { JoinEditor } from './utils/JoinEditor.js';
  31. import { CheckerEditor } from './procedural/CheckerEditor.js';
  32. import { PointsEditor } from './scene/PointsEditor.js';
  33. import { MeshEditor } from './scene/MeshEditor.js';
  34. import { FileEditor } from './core/FileEditor.js';
  35. import { FileURLEditor } from './core/FileURLEditor.js';
  36. import { EventDispatcher } from 'three';
  37. Styles.icons.unlink = 'ti ti-unlink';
  38. export const NodeList = [
  39. {
  40. name: 'Inputs',
  41. icon: 'forms',
  42. children: [
  43. {
  44. name: 'Slider',
  45. icon: 'adjustments-horizontal',
  46. tags: 'number',
  47. nodeClass: SliderEditor
  48. },
  49. {
  50. name: 'Float',
  51. icon: 'box-multiple-1',
  52. nodeClass: FloatEditor
  53. },
  54. {
  55. name: 'Vector 2',
  56. icon: 'box-multiple-2',
  57. nodeClass: Vector2Editor
  58. },
  59. {
  60. name: 'Vector 3',
  61. icon: 'box-multiple-3',
  62. nodeClass: Vector3Editor
  63. },
  64. {
  65. name: 'Vector 4',
  66. icon: 'box-multiple-4',
  67. nodeClass: Vector4Editor
  68. },
  69. {
  70. name: 'Color',
  71. icon: 'palette',
  72. nodeClass: ColorEditor
  73. },
  74. {
  75. name: 'Texture',
  76. icon: 'photo',
  77. nodeClass: TextureEditor
  78. },
  79. {
  80. name: 'File URL',
  81. icon: 'cloud-download',
  82. nodeClass: FileURLEditor
  83. }
  84. ]
  85. },
  86. {
  87. name: 'Accessors',
  88. icon: 'vector-triangle',
  89. children: [
  90. {
  91. name: 'UV',
  92. icon: 'details',
  93. nodeClass: UVEditor
  94. },
  95. {
  96. name: 'Position',
  97. icon: 'hierarchy',
  98. nodeClass: PositionEditor
  99. },
  100. {
  101. name: 'Normal',
  102. icon: 'fold-up',
  103. nodeClass: NormalEditor
  104. },
  105. {
  106. name: 'Matcap UV',
  107. icon: 'circle',
  108. nodeClass: MatcapUVEditor
  109. }
  110. ]
  111. },
  112. {
  113. name: 'Display',
  114. icon: 'brightness',
  115. children: [
  116. {
  117. name: 'Blend',
  118. icon: 'layers-subtract',
  119. tags: 'mix',
  120. nodeClass: BlendEditor
  121. },
  122. {
  123. name: 'Normal Map',
  124. icon: 'chart-line',
  125. nodeClass: NormalMapEditor
  126. }
  127. ]
  128. },
  129. {
  130. name: 'Math',
  131. icon: 'calculator',
  132. children: [
  133. {
  134. name: 'Operator',
  135. icon: 'math-symbols',
  136. tags: 'addition, subtration, multiplication, division',
  137. nodeClass: OperatorEditor
  138. },
  139. {
  140. name: 'Invert',
  141. icon: 'flip-vertical',
  142. tip: 'Negate',
  143. nodeClass: InvertEditor
  144. },
  145. {
  146. name: 'Limiter',
  147. icon: 'arrow-bar-to-up',
  148. tip: 'Min / Max',
  149. nodeClass: LimiterEditor
  150. },
  151. {
  152. name: 'Dot Product',
  153. icon: 'arrows-up-left',
  154. nodeClass: DotEditor
  155. },
  156. {
  157. name: 'Power',
  158. icon: 'arrow-up-right',
  159. nodeClass: PowerEditor
  160. },
  161. {
  162. name: 'Trigonometry',
  163. icon: 'wave-sine',
  164. tip: 'Sin / Cos / Tan / ...',
  165. tags: 'sin, cos, tan, asin, acos, atan, sine, cosine, tangent, arcsine, arccosine, arctangent',
  166. nodeClass: TrigonometryEditor
  167. },
  168. {
  169. name: 'Angle',
  170. icon: 'angle',
  171. tip: 'Degress / Radians',
  172. tags: 'degress, radians',
  173. nodeClass: AngleEditor
  174. },
  175. {
  176. name: 'Normalize',
  177. icon: 'fold',
  178. nodeClass: NormalizeEditor
  179. }
  180. ]
  181. },
  182. {
  183. name: 'Procedural',
  184. icon: 'infinity',
  185. children: [
  186. {
  187. name: 'Checker',
  188. icon: 'border-outer',
  189. nodeClass: CheckerEditor
  190. }
  191. ]
  192. },
  193. {
  194. name: 'Utils',
  195. icon: 'apps',
  196. children: [
  197. {
  198. name: 'Preview',
  199. icon: 'square-check',
  200. nodeClass: PreviewEditor
  201. },
  202. {
  203. name: 'Timer',
  204. icon: 'clock',
  205. nodeClass: TimerEditor
  206. },
  207. {
  208. name: 'Oscillator',
  209. icon: 'wave-sine',
  210. nodeClass: OscillatorEditor
  211. },
  212. {
  213. name: 'Split',
  214. icon: 'arrows-split-2',
  215. nodeClass: SplitEditor
  216. },
  217. {
  218. name: 'Join',
  219. icon: 'arrows-join-2',
  220. nodeClass: JoinEditor
  221. }
  222. ]
  223. },
  224. /*{
  225. name: 'Scene',
  226. icon: '3d-cube-sphere',
  227. children: [
  228. {
  229. name: 'Mesh',
  230. icon: '3d-cube-sphere',
  231. nodeClass: MeshEditor
  232. }
  233. ]
  234. },*/
  235. {
  236. name: 'Material',
  237. icon: 'circles',
  238. children: [
  239. {
  240. name: 'Basic Material',
  241. icon: 'circle',
  242. nodeClass: BasicMaterialEditor
  243. },
  244. {
  245. name: 'Standard Material',
  246. icon: 'circle',
  247. nodeClass: StandardMaterialEditor
  248. },
  249. {
  250. name: 'Points Material',
  251. icon: 'circle-dotted',
  252. nodeClass: PointsMaterialEditor
  253. }
  254. ]
  255. }
  256. ];
  257. export const ClassLib = {
  258. BasicMaterialEditor,
  259. StandardMaterialEditor,
  260. PointsMaterialEditor,
  261. PointsEditor,
  262. MeshEditor,
  263. OperatorEditor,
  264. NormalizeEditor,
  265. InvertEditor,
  266. LimiterEditor,
  267. DotEditor,
  268. PowerEditor,
  269. AngleEditor,
  270. TrigonometryEditor,
  271. FloatEditor,
  272. Vector2Editor,
  273. Vector3Editor,
  274. Vector4Editor,
  275. SliderEditor,
  276. ColorEditor,
  277. TextureEditor,
  278. BlendEditor,
  279. NormalMapEditor,
  280. UVEditor,
  281. MatcapUVEditor,
  282. PositionEditor,
  283. NormalEditor,
  284. TimerEditor,
  285. OscillatorEditor,
  286. SplitEditor,
  287. JoinEditor,
  288. CheckerEditor,
  289. FileURLEditor
  290. };
  291. export class NodeEditor extends EventDispatcher {
  292. constructor( scene = null ) {
  293. super();
  294. const domElement = document.createElement( 'flow' );
  295. const canvas = new Canvas();
  296. domElement.append( canvas.dom );
  297. this.scene = scene;
  298. this.canvas = canvas;
  299. this.domElement = domElement;
  300. this._preview = false;
  301. this.search = null;
  302. this.menu = null;
  303. this.previewMenu = null;
  304. this.nodesContext = null;
  305. this.examplesContext = null;
  306. this._initUpload();
  307. this._initTips();
  308. this._initMenu();
  309. this._initSearch();
  310. this._initNodesContext();
  311. this._initExamplesContext();
  312. }
  313. setSize( width, height ) {
  314. this.canvas.setSize( width, height );
  315. return this;
  316. }
  317. centralizeNode( node ) {
  318. const canvas = this.canvas;
  319. const nodeRect = node.dom.getBoundingClientRect();
  320. node.setPosition(
  321. ( ( canvas.width / 2 ) - canvas.scrollLeft ) - nodeRect.width,
  322. ( ( canvas.height / 2 ) - canvas.scrollTop ) - nodeRect.height
  323. );
  324. return this;
  325. }
  326. add( node ) {
  327. const onRemove = () => {
  328. node.removeEventListener( 'remove', onRemove );
  329. node.setEditor( null );
  330. };
  331. node.setEditor( this );
  332. node.addEventListener( 'remove', onRemove );
  333. this.canvas.add( node );
  334. this.dispatchEvent( { type: 'add', node } );
  335. return this;
  336. }
  337. get nodes() {
  338. return this.canvas.nodes;
  339. }
  340. set preview( value ) {
  341. if ( this._preview === value ) return;
  342. if ( value ) {
  343. this.menu.dom.remove();
  344. this.canvas.dom.remove();
  345. this.search.dom.remove();
  346. this.domElement.append( this.previewMenu.dom );
  347. } else {
  348. this.canvas.focusSelected = false;
  349. this.domElement.append( this.menu.dom );
  350. this.domElement.append( this.canvas.dom );
  351. this.domElement.append( this.search.dom );
  352. this.previewMenu.dom.remove();
  353. }
  354. this._preview = value;
  355. }
  356. get preview() {
  357. return this._preview;
  358. }
  359. newProject() {
  360. const canvas = this.canvas;
  361. canvas.clear();
  362. canvas.scrollLeft = 0;
  363. canvas.scrollTop = 0;
  364. canvas.zoom = 1;
  365. this.dispatchEvent( { type: 'new' } );
  366. }
  367. loadJSON( json ) {
  368. const canvas = this.canvas;
  369. canvas.clear();
  370. canvas.deserialize( json );
  371. for ( const node of canvas.nodes ) {
  372. this.add( node );
  373. }
  374. this.dispatchEvent( { type: 'load' } );
  375. }
  376. _initUpload() {
  377. const canvas = this.canvas;
  378. canvas.onDrop( () => {
  379. for ( const item of canvas.droppedItems ) {
  380. if ( /^image\//.test( item.type ) === true ) {
  381. const { relativeClientX, relativeClientY } = canvas;
  382. const file = item.getAsFile();
  383. const fileEditor = new FileEditor( file );
  384. fileEditor.setPosition(
  385. relativeClientX - ( fileEditor.getWidth() / 2 ),
  386. relativeClientY - 20
  387. );
  388. this.add( fileEditor );
  389. }
  390. }
  391. } );
  392. }
  393. _initTips() {
  394. this.tips = new Tips();
  395. this.domElement.append( this.tips.dom );
  396. }
  397. _initMenu() {
  398. const menu = new CircleMenu();
  399. const previewMenu = new CircleMenu();
  400. menu.setAlign( 'top left' );
  401. previewMenu.setAlign( 'top left' );
  402. const previewButton = new ButtonInput().setIcon( 'ti ti-3d-cube-sphere' ).setToolTip( 'Preview' );
  403. const menuButton = new ButtonInput().setIcon( 'ti ti-apps' ).setToolTip( 'Add' );
  404. const examplesButton = new ButtonInput().setIcon( 'ti ti-file-symlink' ).setToolTip( 'Examples' );
  405. const newButton = new ButtonInput().setIcon( 'ti ti-file' ).setToolTip( 'New' );
  406. const openButton = new ButtonInput().setIcon( 'ti ti-upload' ).setToolTip( 'Open' );
  407. const saveButton = new ButtonInput().setIcon( 'ti ti-download' ).setToolTip( 'Save' );
  408. const editorButton = new ButtonInput().setIcon( 'ti ti-subtask' ).setToolTip( 'Editor' );
  409. previewButton.onClick( () => this.preview = true );
  410. editorButton.onClick( () => this.preview = false );
  411. menuButton.onClick( () => this.nodesContext.open() );
  412. examplesButton.onClick( () => this.examplesContext.open() );
  413. newButton.onClick( () => {
  414. if ( confirm( 'Are you sure?' ) === true ) {
  415. this.newProject();
  416. }
  417. } );
  418. openButton.onClick( () => {
  419. const input = document.createElement( 'input' );
  420. input.type = 'file';
  421. input.onchange = e => {
  422. const file = e.target.files[ 0 ];
  423. const reader = new FileReader();
  424. reader.readAsText( file, 'UTF-8' );
  425. reader.onload = readerEvent => {
  426. const loader = new Loader( Loader.OBJECTS );
  427. const json = loader.parse( JSON.parse( readerEvent.target.result ), ClassLib );
  428. this.loadJSON( json );
  429. };
  430. };
  431. input.click();
  432. } );
  433. saveButton.onClick( () => {
  434. const json = JSON.stringify( this.canvas.toJSON() );
  435. const a = document.createElement( 'a' );
  436. const file = new Blob( [ json ], { type: 'text/plain' } );
  437. a.href = URL.createObjectURL( file );
  438. a.download = 'node_editor.json';
  439. a.click();
  440. } );
  441. menu.add( previewButton )
  442. .add( newButton )
  443. .add( examplesButton )
  444. .add( openButton )
  445. .add( saveButton )
  446. .add( menuButton );
  447. previewMenu.add( editorButton );
  448. this.domElement.append( menu.dom );
  449. this.menu = menu;
  450. this.previewMenu = previewMenu;
  451. }
  452. _initExamplesContext() {
  453. const context = new ContextMenu();
  454. //**************//
  455. // MAIN
  456. //**************//
  457. const onClickExample = async ( button ) => {
  458. this.examplesContext.hide();
  459. const filename = button.getExtra();
  460. const loader = new Loader( Loader.OBJECTS );
  461. const json = await loader.load( `./jsm/node-editor/examples/${filename}.json`, ClassLib );
  462. this.loadJSON( json );
  463. };
  464. const addExample = ( context, name, filename = null ) => {
  465. filename = filename || name.replaceAll( ' ', '-' ).toLowerCase();
  466. context.add( new ButtonInput( name )
  467. .setIcon( 'ti ti-file-symlink' )
  468. .onClick( onClickExample )
  469. .setExtra( filename )
  470. );
  471. };
  472. //**************//
  473. // EXAMPLES
  474. //**************//
  475. const basicContext = new ContextMenu();
  476. const advancedContext = new ContextMenu();
  477. addExample( basicContext, 'Animate UV' );
  478. addExample( basicContext, 'Fake top light' );
  479. addExample( basicContext, 'Oscillator color' );
  480. addExample( advancedContext, 'Rim' );
  481. //**************//
  482. // MAIN
  483. //**************//
  484. context.add( new ButtonInput( 'Basic' ), basicContext );
  485. context.add( new ButtonInput( 'Advanced' ), advancedContext );
  486. this.examplesContext = context;
  487. }
  488. _initSearch() {
  489. const traverseNodeEditors = ( item ) => {
  490. if ( item.nodeClass ) {
  491. const button = new ButtonInput( item.name );
  492. button.setIcon( `ti ti-${item.icon}` );
  493. button.addEventListener( 'complete', () => {
  494. const node = new item.nodeClass();
  495. this.add( node );
  496. this.centralizeNode( node );
  497. this.canvas.select( node );
  498. } );
  499. search.add( button );
  500. if ( item.tags !== undefined ) {
  501. search.setTag( button, item.tags );
  502. }
  503. }
  504. if ( item.children ) {
  505. for ( const subItem of item.children ) {
  506. traverseNodeEditors( subItem );
  507. }
  508. }
  509. };
  510. const search = new Search();
  511. search.forceAutoComplete = true;
  512. search.onFilter( () => {
  513. search.clear();
  514. for ( const item of NodeList ) {
  515. traverseNodeEditors( item );
  516. }
  517. const object3d = this.scene;
  518. if ( object3d !== null ) {
  519. object3d.traverse( ( obj3d ) => {
  520. if ( obj3d.isMesh === true || obj3d.isPoints === true ) {
  521. let prefix = null;
  522. let icon = null;
  523. let editorClass = null;
  524. if ( obj3d.isMesh === true ) {
  525. prefix = 'Mesh';
  526. icon = 'ti ti-3d-cube-sphere';
  527. editorClass = MeshEditor;
  528. } else if ( obj3d.isPoints === true ) {
  529. prefix = 'Points';
  530. icon = 'ti ti-border-none';
  531. editorClass = PointsEditor;
  532. }
  533. const button = new ButtonInput( `${prefix} - ${obj3d.name}` );
  534. button.setIcon( icon );
  535. button.addEventListener( 'complete', () => {
  536. for ( const node of this.canvas.nodes ) {
  537. if ( node.value === obj3d ) {
  538. // prevent duplicated node
  539. this.canvas.select( node );
  540. return;
  541. }
  542. }
  543. const node = new editorClass( obj3d );
  544. this.add( node );
  545. this.centralizeNode( node );
  546. this.canvas.select( node );
  547. } );
  548. search.add( button );
  549. }
  550. } );
  551. }
  552. } );
  553. search.onSubmit( () => {
  554. if ( search.currentFiltered !== null ) {
  555. search.currentFiltered.button.dispatchEvent( new Event( 'complete' ) );
  556. }
  557. } );
  558. this.search = search;
  559. this.domElement.append( search.dom );
  560. }
  561. _initNodesContext() {
  562. const context = new ContextMenu( this.canvas.canvas ).setWidth( 300 );
  563. let isContext = false;
  564. const contextPosition = {};
  565. const add = ( node ) => {
  566. if ( isContext ) {
  567. node.setPosition(
  568. Math.round( contextPosition.x ),
  569. Math.round( contextPosition.y )
  570. );
  571. } else {
  572. this.centralizeNode( node );
  573. this.canvas.select( node );
  574. }
  575. context.hide();
  576. this.add( node );
  577. this.canvas.select( node );
  578. isContext = false;
  579. };
  580. context.onContext( () => {
  581. isContext = true;
  582. const { relativeClientX, relativeClientY } = this.canvas;
  583. contextPosition.x = Math.round( relativeClientX );
  584. contextPosition.y = Math.round( relativeClientY );
  585. } );
  586. context.addEventListener( 'show', () => {
  587. reset();
  588. focus();
  589. } );
  590. //**************//
  591. // INPUTS
  592. //**************//
  593. const nodeButtons = [];
  594. let nodeButtonsVisible = [];
  595. let nodeButtonsIndex = - 1;
  596. const focus = () => requestAnimationFrame( () => search.inputDOM.focus() );
  597. const reset = () => {
  598. search.setValue( '' );
  599. for ( const button of nodeButtons ) {
  600. button.setOpened( false ).setVisible( true ).setSelected( false );
  601. }
  602. };
  603. const node = new Node();
  604. context.add( node );
  605. const search = new StringInput().setPlaceHolder( 'Search...' ).setIcon( 'ti ti-list-search' );
  606. search.inputDOM.addEventListener( 'keydown', e => {
  607. const key = e.key;
  608. if ( key === 'ArrowDown' ) {
  609. const previous = nodeButtonsVisible[ nodeButtonsIndex ];
  610. if ( previous ) previous.setSelected( false );
  611. const current = nodeButtonsVisible[ nodeButtonsIndex = ( nodeButtonsIndex + 1 ) % nodeButtonsVisible.length ];
  612. if ( current ) current.setSelected( true );
  613. e.preventDefault();
  614. e.stopImmediatePropagation();
  615. } else if ( key === 'ArrowUp' ) {
  616. const previous = nodeButtonsVisible[ nodeButtonsIndex ];
  617. if ( previous ) previous.setSelected( false );
  618. const current = nodeButtonsVisible[ nodeButtonsIndex > 0 ? -- nodeButtonsIndex : ( nodeButtonsIndex = nodeButtonsVisible.length - 1 ) ];
  619. if ( current ) current.setSelected( true );
  620. e.preventDefault();
  621. e.stopImmediatePropagation();
  622. } else if ( key === 'Enter' ) {
  623. nodeButtonsVisible[ nodeButtonsIndex ].dom.click();
  624. e.preventDefault();
  625. e.stopImmediatePropagation();
  626. }
  627. } );
  628. search.onChange( () => {
  629. const value = search.getValue().toLowerCase();
  630. if ( value.length === 0 ) return reset();
  631. nodeButtonsVisible = [];
  632. nodeButtonsIndex = 0;
  633. for ( const button of nodeButtons ) {
  634. const buttonLabel = button.getLabel().toLowerCase();
  635. button.setVisible( false ).setSelected( false );
  636. let visible = buttonLabel.indexOf( value ) !== - 1;
  637. if ( visible && button.parent !== null ) {
  638. nodeButtonsVisible.push( button );
  639. }
  640. }
  641. for ( const button of nodeButtonsVisible ) {
  642. let parent = button;
  643. while ( parent !== null ) {
  644. parent.setOpened( true ).setVisible( true );
  645. parent = parent.parent;
  646. }
  647. }
  648. nodeButtonsVisible[ nodeButtonsIndex ].setSelected( true );
  649. } );
  650. const treeView = new TreeViewInput();
  651. node.add( new Element().setHeight( 30 ).add( search ) );
  652. node.add( new Element().setHeight( 200 ).add( treeView ) );
  653. const createButtonMenu = ( item ) => {
  654. const button = new TreeViewNode( item.name );
  655. button.setIcon( `ti ti-${item.icon}` );
  656. if ( item.nodeClass ) {
  657. button.onClick( () => add( new item.nodeClass() ) );
  658. }
  659. if ( item.tip ) {
  660. //button.setToolTip( item.tip );
  661. }
  662. nodeButtons.push( button );
  663. if ( item.children ) {
  664. for ( const subItem of item.children ) {
  665. const subButton = createButtonMenu( subItem );
  666. button.add( subButton );
  667. }
  668. }
  669. return button;
  670. };
  671. for ( const item of NodeList ) {
  672. const button = createButtonMenu( item );
  673. treeView.add( button );
  674. }
  675. this.nodesContext = context;
  676. }
  677. }