BlocksArea.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. 'use strict'
  2. const sum = m => (m.reduce((a, b) => a + b, 0));
  3. const default_elements = {
  4. 'INPUT': {
  5. 'inputs': [],
  6. 'outputs': ['x'],
  7. 'inputs_groups': [],
  8. 'outputs_groups': [1]
  9. },
  10. 'OUTPUT': {
  11. 'inputs': ['x'],
  12. 'outputs': [],
  13. 'inputs_groups': [1],
  14. 'outputs_groups': []
  15. },
  16. 'NOT': {
  17. 'inputs': ['x'],
  18. 'outputs': ['!x'],
  19. 'inputs_groups': [1],
  20. 'outputs_groups': [1]
  21. },
  22. 'AND': {
  23. 'inputs': ['x', 'y'],
  24. 'outputs': ['x && y'],
  25. 'inputs_groups': [1, 1],
  26. 'outputs_groups': [1]
  27. },
  28. 'OR': {
  29. 'inputs': ['x', 'y'],
  30. 'outputs': ['x || y'],
  31. 'inputs_groups': [1, 1],
  32. 'outputs_groups': [1]
  33. }
  34. }
  35. function scalePoint(point, scale) {
  36. return {
  37. 'x': point.x * scale,
  38. 'y': point.y * scale
  39. }
  40. }
  41. function getUniqueId(some_dict) {
  42. return (Object.keys(some_dict).length == 0) ? 0 : (Math.max(...Object.keys(some_dict)) + 1);
  43. }
  44. function joinWithNext(array, i) {
  45. if (array.length > (i + 1)) {
  46. array[i] += array[i + 1];
  47. array.splice(i + 1, 1);
  48. }
  49. return array;
  50. }
  51. function unjoinToNext(array, i) {
  52. if (array[i] > 1) {
  53. array[i] -= 1;
  54. array.splice(i + 1, 0, 1);
  55. }
  56. return array;
  57. }
  58. function cutToSum(array, sum) {
  59. const result = [];
  60. let left = sum;
  61. for (let n of array) {
  62. if (left == 0)
  63. break;
  64. if (n > left)
  65. n = left;
  66. left -= n;
  67. result.push(n);
  68. }
  69. for (let i = 0; i < left; i++)
  70. result.push(1);
  71. return result;
  72. }
  73. function numberOr(x, i) {
  74. x = Number.parseInt(x, 10);
  75. return isNaN(x) ? i : x;
  76. }
  77. function deepCopy(o) {
  78. return JSON.parse(JSON.stringify(o));
  79. }
  80. class BlocksArea extends React.Component {
  81. constructor(props) {
  82. super(props);
  83. this.state = {
  84. 'name': 'test',
  85. 'custom_elements': {},
  86. 'new_element': {
  87. 'type': 'new',
  88. 'inputs': [''],
  89. 'outputs': [''],
  90. 'inputs_groups': [1],
  91. 'outputs_groups': [1]
  92. },
  93. 'inputs_number': 0,
  94. 'outputs_number': 0,
  95. 'blocks': {},
  96. 'wires': {},
  97. 'adding_wire': false,
  98. 'adding_wire_info': undefined,
  99. 'dragging_block': false,
  100. 'scale': 1,
  101. 'offset': {
  102. 'x': 0,
  103. 'y': 0
  104. },
  105. 'dragging_scheme': false,
  106. 'dragging_scheme_from_point': undefined,
  107. 'tests_editor_opened': false,
  108. 'tests': []
  109. };
  110. this.onBlockStateChange = this.onBlockStateChange.bind(this);
  111. this.onBlockMounted = this.onBlockMounted.bind(this);
  112. this.onBlockStopInitialDragging = this.onBlockStopInitialDragging.bind(this);
  113. this.save = this.save.bind(this);
  114. this.load = this.load.bind(this);
  115. this.export = this.export.bind(this);
  116. this.clear = this.clear.bind(this);
  117. this.handleMouseDown = this.handleMouseDown.bind(this);
  118. this.handleMouseDownOnSchemeArea = this.handleMouseDownOnSchemeArea.bind(this);
  119. this.handleMouseMove = this.handleMouseMove.bind(this);
  120. this.handleMouseUp = this.handleMouseUp.bind(this);
  121. this.startAddingWire = this.startAddingWire.bind(this);
  122. this.handleMouseUpOnInputOutput = this.handleMouseUpOnInputOutput.bind(this);
  123. this.handleAddBlockButtonClick = this.handleAddBlockButtonClick.bind(this);
  124. this.handleMouseWheel = this.handleMouseWheel.bind(this);
  125. this.removeWires = this.removeWires.bind(this);
  126. this.getTypeInfo = this.getTypeInfo.bind(this);
  127. this._ref = React.createRef();
  128. this.inputs_number_ref = React.createRef();
  129. this.outputs_number_ref = React.createRef();
  130. this.state.blocks_wrapper_ref = React.createRef();
  131. }
  132. componentDidMount() {
  133. this.state.event_listeners = [
  134. [this.inputs_number_ref.current, 'wheel', e => {
  135. e.preventDefault();
  136. const delta = -e.deltaY / 100;
  137. this.setState(state => {
  138. const new_number = state.new_element.inputs.length + delta;
  139. if (new_number < 1)
  140. return state;
  141. state.new_element.inputs = filledArray(new_number, '');
  142. state.new_element.inputs_groups = cutToSum(
  143. state.new_element.inputs_groups,
  144. new_number
  145. );
  146. return state;
  147. });
  148. }],
  149. [this.outputs_number_ref.current, 'wheel', e => {
  150. e.preventDefault();
  151. const delta = -e.deltaY / 100;
  152. this.setState(state => {
  153. const new_number = numberOr(state.new_element.outputs.length, 1) + delta;
  154. if (new_number < 1)
  155. return state;
  156. state.new_element.outputs = filledArray(new_number, '');
  157. state.new_element.outputs_groups = cutToSum(
  158. state.new_element.outputs_groups,
  159. new_number
  160. );
  161. return state;
  162. });
  163. }],
  164. //fucking drag and drop
  165. [this._ref.current, 'drag', e => e.preventDefault()],
  166. [this._ref.current, 'dragstart', e => e.preventDefault()],
  167. [this._ref.current, 'dragend', e => e.preventDefault()],
  168. [this._ref.current, 'dragover', e => e.preventDefault()],
  169. [this._ref.current, 'dragenter', e => e.preventDefault()],
  170. [this._ref.current, 'dragleave', e => e.preventDefault()],
  171. [this._ref.current, 'drop', e => e.preventDefault()]
  172. ];
  173. for (const e_l of this.state.event_listeners)
  174. e_l[0].addEventListener(e_l[1], e_l[2]);
  175. }
  176. componentWillUnmount() {
  177. for (const e_l of this.state.event_listeners)
  178. e_l[0].removeEventListener(e_l[1], e_l[2]);
  179. }
  180. add(data, wire_type_relative_to_block) {
  181. this.setState(state => {
  182. if (!('blocks' in data))
  183. return state;
  184. for (const b of data.blocks) {
  185. const current_const_ids = Object.values(state.blocks).map(v => v.const_id);
  186. const const_id = (current_const_ids.length == 0) ? 1 : (Math.max(...current_const_ids) + 1);
  187. if (state.blocks[const_id] != undefined)
  188. return state;
  189. if (b.type == 'INPUT'){
  190. b.id = b.type + ' ' + (state.inputs_number + 1);
  191. state.inputs_number += 1;
  192. }
  193. else if (b.type == 'OUTPUT') {
  194. b.id = b.type + ' ' + (state.outputs_number + 1);
  195. state.outputs_number += 1;
  196. }
  197. else {
  198. const dict_with_blocks_with_such_type = Object.fromEntries(
  199. Object.entries(this.state.blocks).filter(([k,v]) =>
  200. v.type == b.type
  201. ));
  202. b.id = b.type + ' ' + (Object.keys(dict_with_blocks_with_such_type).length + 1);
  203. }
  204. b.const_id = const_id;
  205. state.blocks[const_id] = b;
  206. }
  207. return state;
  208. }, () => {
  209. if (!('wires' in data))
  210. return;
  211. this.render();
  212. this.setState(state => {
  213. for (const w of data.wires) {
  214. const new_id = getUniqueId(state.wires);
  215. state.wires[new_id] = {
  216. 'id': new_id,
  217. 'from_block_const_id': String(w.from_block_const_id),
  218. 'to_block_const_id': String(w.to_block_const_id),
  219. 'from_output_id': w.from_output_id,
  220. 'to_input_id': w.to_input_id
  221. };
  222. const b_to = state.blocks[w.to_block_const_id]
  223. const b_from = state.blocks[w.from_block_const_id]
  224. state.blocks[w.to_block_const_id] = b_to.getInfo();
  225. state.blocks[w.from_block_const_id] = b_from.getInfo();
  226. this.updateWireCoordinates(state, new_id, wire_type_relative_to_block, true);
  227. }
  228. return state;
  229. });
  230. });
  231. }
  232. updateWireCoordinates(state, wire_id, type_relative_to_block, convert=true) {
  233. const wire = state.wires[wire_id];
  234. const from_block = state.blocks[wire.from_block_const_id];
  235. const to_block = state.blocks[wire.to_block_const_id];
  236. const blocks_wrapper_element = this._ref.current.parentElement;
  237. const blocks_wrapper_rect = blocks_wrapper_element.getBoundingClientRect();
  238. const scale = this.state.scale;
  239. const convertCoordinates = convert ? (p => ({
  240. 'x': p.x / scale,
  241. 'y': p.y / scale
  242. })) : p => p;
  243. if (type_relative_to_block != 'to')
  244. wire.from_point =
  245. convertCoordinates(from_block.output_connectors_coordinates[wire.from_output_id]);
  246. if (type_relative_to_block != 'from')
  247. wire.to_point =
  248. convertCoordinates(to_block.input_connectors_coordinates[wire.to_input_id]);
  249. state.wires[wire_id] = wire;
  250. }
  251. onBlockMounted(detail) {
  252. this.setState(state => {
  253. state.blocks[detail.const_id] = Object.assign(detail);
  254. return state;
  255. });
  256. }
  257. onBlockStateChange(detail) {
  258. this.setState(state => {
  259. state.blocks[detail.const_id] = detail;
  260. Object.values(state.wires).forEach(w => {
  261. if (detail.const_id == w.from_block_const_id) {
  262. this.updateWireCoordinates(state, w.id, 'from', true);
  263. }
  264. else if (detail.const_id == w.to_block_const_id) {
  265. this.updateWireCoordinates(state, w.id, 'to', true);
  266. }
  267. });
  268. return state;
  269. });
  270. }
  271. getTypeInfo(type_name) {
  272. if (type_name in default_elements)
  273. return Object.assign({}, default_elements[type_name]);
  274. else if (type_name in this.state.custom_elements)
  275. return Object.assign({}, this.state.custom_elements[type_name]);
  276. }
  277. onBlockStopInitialDragging(block_id) {
  278. this.setState(state => {
  279. state.blocks[block_id].dragging = false;
  280. return state;
  281. });
  282. }
  283. getSaveData() {
  284. return {
  285. 'name': this.state.name,
  286. 'scale': this.state.scale,
  287. 'offset': this.state.offset,
  288. 'custom_elements': this.state.custom_elements,
  289. 'new_element': this.state.new_element,
  290. 'inputs_number': this.state.inputs_number,
  291. 'outputs_number': this.state.outputs_number,
  292. 'blocks': this.state.blocks,
  293. 'wires': this.state.wires,
  294. 'tests': this.state.tests
  295. };
  296. }
  297. getSaveName() {
  298. const today = new Date();
  299. const current_date = today.getFullYear().toString()
  300. + '-' + (today.getMonth() + 1).toString()
  301. + '-' + today.getDate().toString();
  302. const current_time = today.getHours()
  303. + ":" + today.getMinutes()
  304. + ":" + today.getSeconds();
  305. return 'logic-scheme'
  306. + '-' + this.state.name
  307. + '-' + current_date
  308. + '-' + current_time
  309. + '.json';
  310. }
  311. save() {
  312. const data = this.getSaveData();
  313. const data_text = JSON.stringify(data, null, '\t');
  314. const name = this.getSaveName();
  315. downloadFile(name, data_text);
  316. }
  317. setLoadData(data) {
  318. this.setState(data);
  319. }
  320. load() {
  321. uploadFile('json', jsonText => {
  322. const data = JSON.parse(jsonText);
  323. this.setLoadData(data);
  324. });
  325. }
  326. getExportData() {
  327. console.log('getExportData, state:', this.state);
  328. const blocks = this.state.blocks;
  329. const tests = this.state.tests;
  330. const unpacked_wires = [];
  331. Object.values(this.state.wires).forEach(w => {
  332. const from_block = blocks[w.from_block_const_id];
  333. const to_block = blocks[w.to_block_const_id];
  334. const group_size = from_block.outputs_groups[w.from_output_id];
  335. const from_block_type = blocks[w.from_block_const_id].type;
  336. const to_block_type = blocks[w.to_block_const_id].type;
  337. const outputs_before_output = blocks[w.from_block_const_id].outputs_groups.slice(0, w.from_output_id);
  338. const first_output_index = sum(outputs_before_output);
  339. const inputs_before_input = blocks[w.to_block_const_id].inputs_groups.slice(0, w.to_input_id);
  340. const first_input_index = sum(inputs_before_input);
  341. for (let i = 0; i < group_size; i++) {
  342. const from_output_id = first_output_index + i;
  343. const to_input_id = first_input_index + i;
  344. const new_unpucked_wire = {};
  345. if ((from_block_type == 'INPUT') && (from_block.id.includes('-'))) {
  346. const n = from_block.id.split(' ')[1];
  347. console.log('n', n)
  348. const n_splited = n.split('-');
  349. const n_from = Number.parseInt(n_splited[0], 10);
  350. new_unpucked_wire.from = 'INPUT_' + (n_from + i) + '[1]';
  351. }
  352. else {
  353. const id_splited = from_block.id.split(' ');
  354. new_unpucked_wire.from = id_splited[0] + '_' + id_splited[1] + '[' + (from_output_id + 1) + ']';
  355. }
  356. if ((to_block_type == 'OUTPUT') && (to_block.id.includes('-'))) {
  357. const n = to_block.id.split(' ')[1];
  358. const n_splited = n.split('-');
  359. const n_from = Number.parseInt(n_splited[0], 10);
  360. new_unpucked_wire.to = 'OUTPUT_' + (n_from + i) + '[1]';
  361. }
  362. else {
  363. const id_splited = to_block.id.split(' ');
  364. new_unpucked_wire.to = id_splited[0] + '_' + id_splited[1] + '[' + (to_input_id + 1) + ']'
  365. }
  366. unpacked_wires.push(new_unpucked_wire);
  367. }
  368. })
  369. const data = {
  370. [this.state.name]: {
  371. 'wires': unpacked_wires
  372. }
  373. }
  374. if (tests.length > 0)
  375. data[this.state.name]['tests'] = tests.map(t => ({
  376. 'inputs': t.slice(0, this.state.inputs_number),
  377. 'outputs': t.slice(-this.state.outputs_number)
  378. }));
  379. return data;
  380. }
  381. getExportName() {
  382. return this.state.name + '.json';
  383. }
  384. export() {
  385. const data = this.getExportData();
  386. const data_text = JSON.stringify(data, null, '\t');
  387. const name = this.getExportName();
  388. downloadFile(name, data_text);
  389. }
  390. handleMouseDown(e, element_type, element_info) {
  391. if (e.button != 0)
  392. return;
  393. this.add({
  394. 'blocks': [Object.assign(deepCopy(element_info), {
  395. 'type': element_type,
  396. 'x': e.clientX,
  397. 'y': e.clientY,
  398. 'dragging': true
  399. })]
  400. });
  401. }
  402. handleBlockMouseDown(b, mouse_x, mouse_y, button, function_after) {
  403. if (button === 2) {
  404. b.state.removeBlock();
  405. return;
  406. }
  407. this.setState({'dragging_block': true});
  408. const blocks_wrapper_element = b._ref.current.parentElement;
  409. const blocks_wrapper_rect = blocks_wrapper_element.getBoundingClientRect();
  410. const scale = this.state.scale;
  411. b.setState(state => {
  412. state.dragging = true;
  413. state.gripX = (mouse_x - blocks_wrapper_rect.x) / scale - state.x;
  414. state.gripY = (mouse_y - blocks_wrapper_rect.y) / scale - state.y;
  415. return state;
  416. }, function_after);
  417. }
  418. handleMouseDownOnSchemeArea(e) {
  419. if (!e.target.classList.contains('schemeArea'))
  420. return;
  421. const mouse_x = e.clientX;
  422. const mouse_y = e.clientY;
  423. this.setState(state => ({
  424. 'dragging_scheme': true,
  425. 'dragging_scheme_from_point': {
  426. 'x': mouse_x - state.offset.x,
  427. 'y': mouse_y - state.offset.y
  428. }
  429. }));
  430. }
  431. handleMouseMove(e) {
  432. const mouse_x = e.clientX;
  433. const mouse_y = e.clientY;
  434. if (this.state.dragging_scheme) {
  435. this.setState(state => ({
  436. 'offset': {
  437. 'x': mouse_x - state.dragging_scheme_from_point['x'],
  438. 'y': mouse_y - state.dragging_scheme_from_point['y']
  439. }
  440. }));
  441. }
  442. else if (this.state.adding_wire_info) {
  443. const blocks_wrapper_element = this.state.blocks_wrapper_ref.current;
  444. const blocks_wrapper_rect = blocks_wrapper_element.getBoundingClientRect();
  445. const info = this.state.adding_wire_info;
  446. if (info.from_block_const_id == undefined)
  447. this.setState(state => {
  448. state.adding_wire_info.from_point = {
  449. 'x': mouse_x - blocks_wrapper_rect.x,
  450. 'y': mouse_y - blocks_wrapper_rect.y
  451. };
  452. return state;
  453. });
  454. else
  455. this.setState(state => {
  456. state.adding_wire_info.to_point = {
  457. 'x': mouse_x - blocks_wrapper_rect.x,
  458. 'y': mouse_y - blocks_wrapper_rect.y
  459. };
  460. return state;
  461. });
  462. }
  463. }
  464. handleBlockMouseMove(b, mouse_x, mouse_y) {
  465. const blocks_wrapper_element = b._ref.current.parentElement;
  466. const blocks_wrapper_rect = blocks_wrapper_element.getBoundingClientRect();
  467. const scale = this.state.scale;
  468. if (b.state.dragging === true) {
  469. b.setState(state => {
  470. state.x = (mouse_x - blocks_wrapper_rect.x) / scale - state.gripX;
  471. state.y = (mouse_y - blocks_wrapper_rect.y) / scale - state.gripY;
  472. return state;
  473. }, () => b.state.onStateChange(b.getInfo(b.state)));
  474. }
  475. }
  476. handleMouseUp() {
  477. this.setState({
  478. 'adding_wire': false,
  479. 'dragging_block': false,
  480. 'dragging_scheme': false,
  481. 'dragging_scheme_from_point': undefined
  482. });
  483. }
  484. handleMouseUpOnInputOutput(input_output_info) {
  485. if (!this.state.adding_wire)
  486. return;
  487. if (this.state.adding_wire_info.group_size != input_output_info.group_size)
  488. return;
  489. const new_wire_info = Object.assign({}, this.state.adding_wire_info);
  490. this.setState({'adding_wire_info': undefined});
  491. for (const key in input_output_info)
  492. new_wire_info[key] = input_output_info[key];
  493. if (!('to_block_const_id' in new_wire_info) ||
  494. !('from_block_const_id' in new_wire_info) ||
  495. (new_wire_info.to_block_const_id == new_wire_info.from_block_const_id))
  496. return;
  497. delete new_wire_info['from_point'];
  498. delete new_wire_info['to_point'];
  499. this.add({'wires': [new_wire_info]});
  500. }
  501. removeBlock(const_id) {
  502. this.setState(state => {
  503. const type = state.blocks[const_id].type;
  504. const id = state.blocks[const_id].id;
  505. const number = Number.parseInt(id.split(' ').pop(), 10);
  506. state.wires = Object.fromEntries(Object.entries(state.wires).filter(([k,v]) =>
  507. (
  508. (v.from_block_const_id != const_id) &&
  509. (v.to_block_const_id != const_id)
  510. )
  511. ));
  512. let delta = undefined;
  513. if (type == 'INPUT') {
  514. delta = -sum(state.blocks[const_id].getOutputsGroups());
  515. state.inputs_number += delta;
  516. }
  517. else if (type == 'OUTPUT') {
  518. delta = -sum(state.blocks[const_id].getInputsGroups());
  519. state.outputs_number += delta;
  520. }
  521. else
  522. delta = -1;
  523. delete state.blocks[const_id];
  524. this.shiftBlocksIds(state, type, const_id, delta);
  525. return state;
  526. });
  527. }
  528. updateInputsOutputsNames(type, const_id, delta) {
  529. this.setState(state => {
  530. const current_n = state.blocks[const_id].id.split(' ')[1];
  531. let new_current_n = undefined;
  532. if (current_n.includes('-')) {
  533. const current_n_splited = current_n.split('-');
  534. const current_n_from = Number.parseInt(current_n_splited[0], 10);
  535. const current_n_to = Number.parseInt(current_n_splited[1], 10);
  536. const new_n_to = current_n_to + delta;
  537. if (new_n_to == current_n_from)
  538. new_current_n = '' + current_n_from;
  539. else
  540. new_current_n = current_n_from + '-' + (current_n_to + delta);
  541. }
  542. else {
  543. const current_n_int = Number.parseInt(current_n, 10);
  544. new_current_n = current_n_int + '-' + (current_n_int + delta);
  545. }
  546. state.blocks[const_id].id = type + ' ' + new_current_n;
  547. this.shiftBlocksIds(state, type, const_id, delta);
  548. if (type == 'INPUT')
  549. state.inputs_number += delta;
  550. else if (type = 'OUTPUT')
  551. state.outputs_number += delta;
  552. return state;
  553. });
  554. }
  555. shiftBlocksIds(state, type, from_const_id, delta) {
  556. for (const k in state.blocks)
  557. if (state.blocks[k].type == type)
  558. if (state.blocks[k].const_id > from_const_id) {
  559. const n = state.blocks[k].id.split(' ')[1];
  560. let new_n = undefined;
  561. if (n.includes('-')) {
  562. const n_splited = n.split('-');
  563. const n_from = Number.parseInt(n_splited[0], 10);
  564. const n_to = Number.parseInt(n_splited[1], 10);
  565. new_n = (n_from + delta) + '-' + (n_to + delta);
  566. }
  567. else {
  568. const n_int = Number.parseInt(n, 10);
  569. new_n = '' + (n_int + delta);
  570. }
  571. const new_id = type + ' ' + new_n;
  572. state.blocks[k].setId(new_id)
  573. state.blocks[k].id = new_id;
  574. }
  575. }
  576. startAddingWire(wire_info) {
  577. this.setState({
  578. 'adding_wire': true,
  579. 'adding_wire_info': wire_info
  580. });
  581. }
  582. removeWires(mask) {
  583. this.setState(state => {
  584. state.wires = Object.fromEntries(Object.entries(state.wires).filter(([k,v]) => {
  585. for (const mask_key in mask)
  586. if (mask[mask_key] != v[mask_key])
  587. return true;
  588. return false;
  589. }));
  590. return state;
  591. });
  592. }
  593. handleMouseWheel(e) {
  594. const delta = 1 + -e.deltaY / 1000;
  595. const mouse_x = e.clientX;
  596. const mouse_y = e.clientY;
  597. this.setState(state => {
  598. state.scale *= delta;
  599. state.offset.x -= (delta - 1) * (mouse_x - state.offset.x);
  600. state.offset.y -= (delta - 1) * (mouse_y - state.offset.y);
  601. return state;
  602. });
  603. }
  604. handleAddBlockButtonClick() {
  605. console.log('handleAddBlockButtonClick');
  606. const name = this.state.new_element.type;
  607. this.setState(state => {
  608. this.state.custom_elements[name] = deepCopy(this.state.new_element);
  609. return state;
  610. }, () => console.log('after handleAddBlockButtonClick', this.state.custom_elements));
  611. }
  612. clear() {
  613. this.setState({
  614. 'blocks': {},
  615. 'wires': {},
  616. 'inputs_number': 0,
  617. 'outputs_number': 0,
  618. 'tests': []
  619. });
  620. }
  621. wireHere(block_const_id, type, index) {
  622. const connectedTo = (const_id, t, i) => (w =>
  623. (
  624. (
  625. (t == 'output') &&
  626. (const_id == w.from_block_const_id) && (i == w.from_output_id)
  627. ) ||
  628. (
  629. (t == 'input') &&
  630. (const_id == w.to_block_const_id) && (i == w.to_input_id)
  631. )
  632. ));
  633. return Object.values(this.state.wires).some(connectedTo(block_const_id, type, index));
  634. }
  635. render() {
  636. const scale = this.state.scale;
  637. const offset = this.state.offset;
  638. const inputs_number = this.state.inputs_number;
  639. const outputs_number = this.state.outputs_number;
  640. const tests_number = this.state.tests.length;
  641. const max_tests_number = 2 ** inputs_number;
  642. return <div className="blocksArea" ref={this._ref}>
  643. <div className="sidePanel"
  644. style={{
  645. 'zIndex': 10,
  646. 'display': this.state.dragging_block ? 'none' : 'block'
  647. }}>
  648. <div className="controls">
  649. <input type="text" className="schemeName unselectable"
  650. value={this.state.name}
  651. onChange={e => this.setState({'name': e.target.value})}></input>
  652. <button className="exportButton animated animated-lightblue unselectable"
  653. onClick={this.export}>export</button>
  654. <button className="saveButton animated animated-green unselectable"
  655. onClick={this.save}>save</button>
  656. <button className="loadButton animated animated-blue unselectable"
  657. onClick={this.load}>load</button>
  658. <button className="clearButton animated animated-red unselectable"
  659. onClick={this.clear}>clear</button>
  660. </div>
  661. <div className="tests">
  662. {
  663. ((inputs_number > 0) && (outputs_number > 0)) ?
  664. <div className="coverageInfo unselectable" id={tests_number + ' ' + max_tests_number}>
  665. Coverage:<br></br>{tests_number}/{max_tests_number} ({Math.floor(tests_number / max_tests_number * 100)}%)
  666. </div>
  667. : null
  668. }
  669. <button className="editTestsButton animated animated-lightblue unselectable"
  670. onClick={() => this.setState({'tests_editor_opened': true})}>edit tests</button>
  671. </div>
  672. <div className="newBlockConfiguration">
  673. <div className="block blockToAdd"
  674. onMouseDown={e => this.handleMouseDown(e, this.state.new_element.type, this.state.new_element)}>
  675. <div className="content">
  676. <input type="text" className="name"
  677. value={this.state.new_element.type}
  678. onChange={e => this.setState(state => {
  679. state.new_element.type = e.target.value;
  680. return state;
  681. })}
  682. onMouseDown={e => e.stopPropagation()}></input>
  683. </div>
  684. </div>
  685. <div className="inputsOutputsNumber">
  686. <div className="inputsNumber">
  687. <input type="number" min="1" ref={this.inputs_number_ref}
  688. value={this.state.new_element.inputs.length}
  689. onChange={e => this.setState(state => {
  690. const new_length = numberOr(e.target.value, 1)
  691. state.new_element.inputs = filledArray(new_length, '');
  692. state.new_element.inputs_groups = cutToSum(
  693. state.new_element.inputs_groups,
  694. new_length
  695. );
  696. return state;
  697. })
  698. }></input>
  699. </div>
  700. <div className="outputsNumber">
  701. <input type="number" min="1" ref={this.outputs_number_ref}
  702. value={this.state.new_element.outputs.length}
  703. onChange={e => this.setState(state => {
  704. const new_length = numberOr(e.target.value, 1)
  705. state.new_element.outputs = filledArray(new_length, '')
  706. state.new_element.outputs_groups = cutToSum(
  707. state.new_element.outputs_groups,
  708. new_length
  709. );
  710. return state;
  711. })}></input>
  712. </div>
  713. </div>
  714. <div className="inputsOutputsGroups">
  715. <div className="inputsGroups">
  716. {
  717. this.state.new_element.inputs_groups.map((g, i) =>
  718. <div key={i} className="inputGroup unselectable"
  719. onMouseDown={e => {
  720. let f = undefined;
  721. if (e.button == 0)
  722. f = joinWithNext;
  723. else if (e.button == 2)
  724. f = unjoinToNext;
  725. else
  726. return state;
  727. this.setState(state => {
  728. state.new_element.inputs_groups = f(state.new_element.inputs_groups, i);
  729. return state;
  730. })
  731. }}
  732. onContextMenu={e => e.preventDefault()}>{g}</div>
  733. )
  734. }
  735. </div>
  736. <div className="outputsGroups">
  737. {
  738. this.state.new_element.outputs_groups.map((g, i) =>
  739. <div key={i} className="outputGroup unselectable"
  740. onMouseDown={e => {
  741. let f = undefined;
  742. if (e.button == 0)
  743. f = joinWithNext;
  744. else if (e.button == 2)
  745. f = unjoinToNext;
  746. else
  747. return state;
  748. this.setState(state => {
  749. state.new_element.outputs_groups = f(state.new_element.outputs_groups, i);
  750. return state;
  751. })
  752. }}
  753. onContextMenu={e => e.preventDefault()}>{g}</div>
  754. )
  755. }
  756. </div>
  757. </div>
  758. <button className="addBlockButton animated animated-green unselectable" onClick={this.handleAddBlockButtonClick}>+</button>
  759. </div>
  760. <div className="blocks">
  761. {
  762. Object.entries(this.state.custom_elements).map(
  763. (element_type_and_element, i) =>
  764. <div key={element_type_and_element[0]} className="block"
  765. onMouseDown={e => this.handleMouseDown(e, element_type_and_element[0], element_type_and_element[1])}>
  766. <div className="content">
  767. <div className="name unselectable">{element_type_and_element[0]}</div>
  768. </div>
  769. </div>
  770. )
  771. }
  772. {
  773. Object.entries(default_elements).map(
  774. (element_type_and_element, i) =>
  775. <div key={element_type_and_element[0]} className="block"
  776. onMouseDown={e => this.handleMouseDown(e, element_type_and_element[0], element_type_and_element[1])}>
  777. <div className="content">
  778. <div className="name unselectable">{element_type_and_element[0]}</div>
  779. </div>
  780. </div>
  781. )
  782. }
  783. </div>
  784. </div>
  785. {
  786. this.state.tests_editor_opened ?
  787. <ModalWindow
  788. close_function={() => this.setState({'tests_editor_opened': false})}>
  789. <TestsEditor
  790. inputs={Array.from({length: inputs_number}, (_, i) => i + 1)}
  791. outputs={Array.from({length: outputs_number}, (_, i) => i + 1)}
  792. tests={this.state.tests.map(
  793. t => t.slice(0, inputs_number).concat(t.slice(-outputs_number))
  794. )}
  795. onUnmount={tests => this.setState({'tests': tests})}></TestsEditor>
  796. </ModalWindow>
  797. : null
  798. }
  799. <div className="schemeArea"
  800. onWheel={this.handleMouseWheel}
  801. onMouseDown={this.handleMouseDownOnSchemeArea}
  802. onMouseMove={this.handleMouseMove}
  803. onMouseUp={this.handleMouseUp}
  804. onContextMenu={e => e.preventDefault()}>
  805. <div className="blocksWrapper"
  806. ref={this.state.blocks_wrapper_ref}
  807. style={{
  808. 'left': offset.x,
  809. 'top': offset.y,
  810. 'transform': 'scale(' + scale + ')'
  811. }}>
  812. {
  813. Object.entries(this.state.blocks).map(
  814. (block_id_and_block, i) =>
  815. <Block key={((block_id_and_block[1].type == 'INPUT') || (block_id_and_block[1].type == 'OUTPUT')) ?
  816. (block_id_and_block[0] + ' ' + block_id_and_block[1].id) :
  817. block_id_and_block[0]}
  818. const_id={block_id_and_block[1].const_id}
  819. id={block_id_and_block[1].id}
  820. type={block_id_and_block[1].type}
  821. x={block_id_and_block[1].x}
  822. y={block_id_and_block[1].y}
  823. scale={this.state.scale}
  824. handleMouseMove={this.handleBlockMouseMove.bind(this)}
  825. handleMouseDown={this.handleBlockMouseDown.bind(this)}
  826. dragging={block_id_and_block[1].dragging}
  827. inputs={block_id_and_block[1].inputs}
  828. outputs={block_id_and_block[1].outputs}
  829. inputs_groups={block_id_and_block[1].inputs_groups}
  830. outputs_groups={block_id_and_block[1].outputs_groups}
  831. removeBlock={() => this.removeBlock(block_id_and_block[0])}
  832. startAddingWire={this.startAddingWire}
  833. handleMouseUpOnInputOutput={this.handleMouseUpOnInputOutput}
  834. removeWires={this.removeWires}
  835. onMount={this.onBlockMounted}
  836. onStateChange={this.onBlockStateChange}
  837. onStopInitialDragging={this.onBlockStopInitialDragging}
  838. updateInputsOutputsNames={this.updateInputsOutputsNames.bind(this)}
  839. wireHere={this.wireHere.bind(this)}
  840. getTypeInfo={this.getTypeInfo.bind(this)}></Block>
  841. )
  842. }
  843. {
  844. Object.values(this.state.wires).map(
  845. wire =>
  846. <Wire key={wire.from_point.x + ' ' + wire.from_point.y + ' ' + wire.to_point.x + ' ' + wire.to_point.y} from_point={wire.from_point} to_point={wire.to_point}
  847. scale={scale}></Wire>
  848. )
  849. }
  850. {
  851. this.state.adding_wire ?
  852. <Wire_f
  853. from_point={scalePoint(this.state.adding_wire_info.from_point, 1 / scale)}
  854. to_point={scalePoint(this.state.adding_wire_info.to_point, 1 / scale)}
  855. scale={scale}>
  856. </Wire_f>
  857. : null
  858. }
  859. </div>
  860. </div>
  861. </div>;
  862. }
  863. }