BlocksArea.js 30 KB

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