NodeEditor.js 18 KB

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