cncjs

Форк
0
/
Workspace.jsx 
721 строка · 23.4 Кб
1
import _ from 'lodash';
2
import classNames from 'classnames';
3
import Dropzone from 'react-dropzone';
4
import pubsub from 'pubsub-js';
5
import React, { PureComponent } from 'react';
6
import ReactDOM from 'react-dom';
7
import { withRouter } from 'react-router-dom';
8
import { Button, ButtonGroup, ButtonToolbar } from 'app/components/Buttons';
9
import api from 'app/api';
10
import {
11
  WORKFLOW_STATE_IDLE
12
} from 'app/constants';
13
import controller from 'app/lib/controller';
14
import i18n from 'app/lib/i18n';
15
import log from 'app/lib/log';
16
import store from 'app/store';
17
import * as widgetManager from './WidgetManager';
18
import DefaultWidgets from './DefaultWidgets';
19
import PrimaryWidgets from './PrimaryWidgets';
20
import SecondaryWidgets from './SecondaryWidgets';
21
import FeederPaused from './modals/FeederPaused';
22
import FeederWait from './modals/FeederWait';
23
import ServerDisconnected from './modals/ServerDisconnected';
24
import styles from './index.styl';
25
import {
26
  MODAL_NONE,
27
  MODAL_FEEDER_PAUSED,
28
  MODAL_FEEDER_WAIT,
29
  MODAL_SERVER_DISCONNECTED
30
} from './constants';
31

32
const WAIT = '%wait';
33

34
const startWaiting = () => {
35
  // Adds the 'wait' class to <html>
36
  const root = document.documentElement;
37
  root.classList.add('wait');
38
};
39
const stopWaiting = () => {
40
  // Adds the 'wait' class to <html>
41
  const root = document.documentElement;
42
  root.classList.remove('wait');
43
};
44

45
class Workspace extends PureComponent {
46
    static propTypes = {
47
      ...withRouter.propTypes
48
    };
49

50
    state = {
51
      mounted: false,
52
      port: '',
53
      modal: {
54
        name: MODAL_NONE,
55
        params: {}
56
      },
57
      isDraggingFile: false,
58
      isDraggingWidget: false,
59
      isUploading: false,
60
      showPrimaryContainer: store.get('workspace.container.primary.show'),
61
      showSecondaryContainer: store.get('workspace.container.secondary.show'),
62
      inactiveCount: _.size(widgetManager.getInactiveWidgets())
63
    };
64

65
    action = {
66
      openModal: (name = MODAL_NONE, params = {}) => {
67
        this.setState(state => ({
68
          modal: {
69
            name: name,
70
            params: params
71
          }
72
        }));
73
      },
74
      closeModal: () => {
75
        this.setState(state => ({
76
          modal: {
77
            name: MODAL_NONE,
78
            params: {}
79
          }
80
        }));
81
      },
82
      updateModalParams: (params = {}) => {
83
        this.setState(state => ({
84
          modal: {
85
            ...state.modal,
86
            params: {
87
              ...state.modal.params,
88
              ...params
89
            }
90
          }
91
        }));
92
      }
93
    };
94

95
    sortableGroup = {
96
      primary: null,
97
      secondary: null
98
    };
99

100
    primaryContainer = null;
101

102
    secondaryContainer = null;
103

104
    primaryToggler = null;
105

106
    secondaryToggler = null;
107

108
    primaryWidgets = null;
109

110
    secondaryWidgets = null;
111

112
    defaultContainer = null;
113

114
    controllerEvents = {
115
      'connect': () => {
116
        if (controller.connected) {
117
          this.action.closeModal();
118
        } else {
119
          this.action.openModal(MODAL_SERVER_DISCONNECTED);
120
        }
121
      },
122
      'connect_error': () => {
123
        if (controller.connected) {
124
          this.action.closeModal();
125
        } else {
126
          this.action.openModal(MODAL_SERVER_DISCONNECTED);
127
        }
128
      },
129
      'disconnect': () => {
130
        if (controller.connected) {
131
          this.action.closeModal();
132
        } else {
133
          this.action.openModal(MODAL_SERVER_DISCONNECTED);
134
        }
135
      },
136
      'serialport:open': (options) => {
137
        const { port } = options;
138
        this.setState({ port: port });
139
      },
140
      'serialport:close': (options) => {
141
        this.setState({ port: '' });
142
      },
143
      'feeder:status': (status) => {
144
        const { modal } = this.state;
145
        const { hold, holdReason } = { ...status };
146

147
        if (!hold) {
148
          if (_.includes([MODAL_FEEDER_PAUSED, MODAL_FEEDER_WAIT], modal.name)) {
149
            this.action.closeModal();
150
          }
151
          return;
152
        }
153

154
        const { err, data, msg } = { ...holdReason };
155

156
        if (err) {
157
          this.action.openModal(MODAL_FEEDER_PAUSED, {
158
            title: i18n._('Error'),
159
            message: msg,
160
          });
161
          return;
162
        }
163

164
        if (data === WAIT) {
165
          this.action.openModal(MODAL_FEEDER_WAIT, {
166
            title: '%wait',
167
            message: msg,
168
          });
169
          return;
170
        }
171

172
        const title = {
173
          'M0': i18n._('M0 Program Pause'),
174
          'M1': i18n._('M1 Program Pause'),
175
          'M2': i18n._('M2 Program End'),
176
          'M30': i18n._('M30 Program End'),
177
          'M6': i18n._('M6 Tool Change'),
178
          'M109': i18n._('M109 Set Extruder Temperature'),
179
          'M190': i18n._('M190 Set Heated Bed Temperature')
180
        }[data] || data;
181

182
        this.action.openModal(MODAL_FEEDER_PAUSED, {
183
          title: title,
184
          message: msg,
185
        });
186
      }
187
    };
188

189
    widgetEventHandler = {
190
      onForkWidget: (widgetId) => {
191
        // TODO
192
      },
193
      onRemoveWidget: (widgetId) => {
194
        const inactiveWidgets = widgetManager.getInactiveWidgets();
195
        this.setState({ inactiveCount: inactiveWidgets.length });
196
      },
197
      onDragStart: () => {
198
        const { isDraggingWidget } = this.state;
199
        if (!isDraggingWidget) {
200
          this.setState({ isDraggingWidget: true });
201
        }
202
      },
203
      onDragEnd: () => {
204
        const { isDraggingWidget } = this.state;
205
        if (isDraggingWidget) {
206
          this.setState({ isDraggingWidget: false });
207
        }
208
      }
209
    };
210

211
    togglePrimaryContainer = () => {
212
      const { showPrimaryContainer } = this.state;
213
      this.setState({ showPrimaryContainer: !showPrimaryContainer });
214

215
      // Publish a 'resize' event
216
      pubsub.publish('resize'); // Also see "widgets/Visualizer"
217
    };
218

219
    toggleSecondaryContainer = () => {
220
      const { showSecondaryContainer } = this.state;
221
      this.setState({ showSecondaryContainer: !showSecondaryContainer });
222

223
      // Publish a 'resize' event
224
      pubsub.publish('resize'); // Also see "widgets/Visualizer"
225
    };
226

227
    resizeDefaultContainer = () => {
228
      const sidebar = document.querySelector('#sidebar');
229
      const primaryContainer = ReactDOM.findDOMNode(this.primaryContainer);
230
      const secondaryContainer = ReactDOM.findDOMNode(this.secondaryContainer);
231
      const primaryToggler = ReactDOM.findDOMNode(this.primaryToggler);
232
      const secondaryToggler = ReactDOM.findDOMNode(this.secondaryToggler);
233
      const defaultContainer = ReactDOM.findDOMNode(this.defaultContainer);
234
      const { showPrimaryContainer, showSecondaryContainer } = this.state;
235

236
      { // Mobile-Friendly View
237
        const { location } = this.props;
238
        const disableHorizontalScroll = !(showPrimaryContainer && showSecondaryContainer);
239

240
        if (location.pathname === '/workspace' && disableHorizontalScroll) {
241
          // Disable horizontal scroll
242
          document.body.scrollLeft = 0;
243
          document.body.style.overflowX = 'hidden';
244
        } else {
245
          // Enable horizontal scroll
246
          document.body.style.overflowX = '';
247
        }
248
      }
249

250
      if (showPrimaryContainer) {
251
        defaultContainer.style.left = primaryContainer.offsetWidth + sidebar.offsetWidth + 'px';
252
      } else {
253
        defaultContainer.style.left = primaryToggler.offsetWidth + sidebar.offsetWidth + 'px';
254
      }
255

256
      if (showSecondaryContainer) {
257
        defaultContainer.style.right = secondaryContainer.offsetWidth + 'px';
258
      } else {
259
        defaultContainer.style.right = secondaryToggler.offsetWidth + 'px';
260
      }
261

262
      // Publish a 'resize' event
263
      pubsub.publish('resize'); // Also see "widgets/Visualizer"
264
    };
265

266
    onDrop = (files) => {
267
      const { port } = this.state;
268

269
      if (!port) {
270
        return;
271
      }
272

273
      let file = files[0];
274
      let reader = new FileReader();
275

276
      reader.onloadend = (event) => {
277
        const { result, error } = event.target;
278

279
        if (error) {
280
          log.error(error);
281
          return;
282
        }
283

284
        log.debug('FileReader:', _.pick(file, [
285
          'lastModified',
286
          'lastModifiedDate',
287
          'meta',
288
          'name',
289
          'size',
290
          'type'
291
        ]));
292

293
        startWaiting();
294
        this.setState({ isUploading: true });
295

296
        const name = file.name;
297
        const gcode = result;
298

299
        api.loadGCode({ port, name, gcode })
300
          .then((res) => {
301
            const { name = '', gcode = '' } = { ...res.body };
302
            pubsub.publish('gcode:load', { name, gcode });
303
          })
304
          .catch((res) => {
305
            log.error('Failed to upload G-code file');
306
          })
307
          .then(() => {
308
            stopWaiting();
309
            this.setState({ isUploading: false });
310
          });
311
      };
312

313
      try {
314
        reader.readAsText(file);
315
      } catch (err) {
316
        // Ignore error
317
      }
318
    };
319

320
    updateWidgetsForPrimaryContainer = () => {
321
      widgetManager.show((activeWidgets, inactiveWidgets) => {
322
        const widgets = Object.keys(store.get('widgets', {}))
323
          .filter(widgetId => {
324
            // e.g. "webcam" or "webcam:d8e6352f-80a9-475f-a4f5-3e9197a48a23"
325
            const name = widgetId.split(':')[0];
326
            return _.includes(activeWidgets, name);
327
          });
328

329
        const defaultWidgets = store.get('workspace.container.default.widgets');
330
        const sortableWidgets = _.difference(widgets, defaultWidgets);
331
        let primaryWidgets = store.get('workspace.container.primary.widgets');
332
        let secondaryWidgets = store.get('workspace.container.secondary.widgets');
333

334
        primaryWidgets = sortableWidgets.slice();
335
        _.pullAll(primaryWidgets, secondaryWidgets);
336
        pubsub.publish('updatePrimaryWidgets', primaryWidgets);
337

338
        secondaryWidgets = sortableWidgets.slice();
339
        _.pullAll(secondaryWidgets, primaryWidgets);
340
        pubsub.publish('updateSecondaryWidgets', secondaryWidgets);
341

342
        // Update inactive count
343
        this.setState({ inactiveCount: _.size(inactiveWidgets) });
344
      });
345
    };
346

347
    updateWidgetsForSecondaryContainer = () => {
348
      widgetManager.show((activeWidgets, inactiveWidgets) => {
349
        const widgets = Object.keys(store.get('widgets', {}))
350
          .filter(widgetId => {
351
            // e.g. "webcam" or "webcam:d8e6352f-80a9-475f-a4f5-3e9197a48a23"
352
            const name = widgetId.split(':')[0];
353
            return _.includes(activeWidgets, name);
354
          });
355

356
        const defaultWidgets = store.get('workspace.container.default.widgets');
357
        const sortableWidgets = _.difference(widgets, defaultWidgets);
358
        let primaryWidgets = store.get('workspace.container.primary.widgets');
359
        let secondaryWidgets = store.get('workspace.container.secondary.widgets');
360

361
        secondaryWidgets = sortableWidgets.slice();
362
        _.pullAll(secondaryWidgets, primaryWidgets);
363
        pubsub.publish('updateSecondaryWidgets', secondaryWidgets);
364

365
        primaryWidgets = sortableWidgets.slice();
366
        _.pullAll(primaryWidgets, secondaryWidgets);
367
        pubsub.publish('updatePrimaryWidgets', primaryWidgets);
368

369
        // Update inactive count
370
        this.setState({ inactiveCount: _.size(inactiveWidgets) });
371
      });
372
    };
373

374
    componentDidMount() {
375
      this.addControllerEvents();
376
      this.addResizeEventListener();
377

378
      setTimeout(() => {
379
        // A workaround solution to trigger componentDidUpdate on initial render
380
        this.setState({ mounted: true });
381
      }, 0);
382
    }
383

384
    componentWillUnmount() {
385
      this.removeControllerEvents();
386
      this.removeResizeEventListener();
387
    }
388

389
    componentDidUpdate() {
390
      store.set('workspace.container.primary.show', this.state.showPrimaryContainer);
391
      store.set('workspace.container.secondary.show', this.state.showSecondaryContainer);
392

393
      this.resizeDefaultContainer();
394
    }
395

396
    addControllerEvents() {
397
      Object.keys(this.controllerEvents).forEach(eventName => {
398
        const callback = this.controllerEvents[eventName];
399
        controller.addListener(eventName, callback);
400
      });
401
    }
402

403
    removeControllerEvents() {
404
      Object.keys(this.controllerEvents).forEach(eventName => {
405
        const callback = this.controllerEvents[eventName];
406
        controller.removeListener(eventName, callback);
407
      });
408
    }
409

410
    addResizeEventListener() {
411
      this.onResizeThrottled = _.throttle(this.resizeDefaultContainer, 50);
412
      window.addEventListener('resize', this.onResizeThrottled);
413
    }
414

415
    removeResizeEventListener() {
416
      window.removeEventListener('resize', this.onResizeThrottled);
417
      this.onResizeThrottled = null;
418
    }
419

420
    render() {
421
      const { style, className } = this.props;
422
      const {
423
        port,
424
        modal,
425
        isDraggingFile,
426
        isDraggingWidget,
427
        showPrimaryContainer,
428
        showSecondaryContainer,
429
        inactiveCount
430
      } = this.state;
431
      const hidePrimaryContainer = !showPrimaryContainer;
432
      const hideSecondaryContainer = !showSecondaryContainer;
433

434
      return (
435
        <div style={style} className={classNames(className, styles.workspace)}>
436
          {modal.name === MODAL_FEEDER_PAUSED && (
437
            <FeederPaused
438
              title={modal.params.title}
439
              message={modal.params.message}
440
              onClose={this.action.closeModal}
441
            />
442
          )}
443
          {modal.name === MODAL_FEEDER_WAIT && (
444
            <FeederWait
445
              title={modal.params.title}
446
              message={modal.params.message}
447
              onClose={this.action.closeModal}
448
            />
449
          )}
450
          {modal.name === MODAL_SERVER_DISCONNECTED &&
451
            <ServerDisconnected />
452
          }
453
          <div
454
            className={classNames(
455
              styles.dropzoneOverlay,
456
              { [styles.hidden]: !(port && isDraggingFile) }
457
            )}
458
          >
459
            <div className={styles.textBlock}>
460
              {i18n._('Drop G-code file here')}
461
            </div>
462
          </div>
463
          <Dropzone
464
            className={styles.dropzone}
465
            disabled={controller.workflow.state !== WORKFLOW_STATE_IDLE}
466
            disableClick={true}
467
            disablePreview={true}
468
            multiple={false}
469
            onDragStart={(event) => {
470
            }}
471
            onDragEnter={(event) => {
472
              if (controller.workflow.state !== WORKFLOW_STATE_IDLE) {
473
                return;
474
              }
475
              if (isDraggingWidget) {
476
                return;
477
              }
478
              if (!isDraggingFile) {
479
                this.setState({ isDraggingFile: true });
480
              }
481
            }}
482
            onDragLeave={(event) => {
483
              if (controller.workflow.state !== WORKFLOW_STATE_IDLE) {
484
                return;
485
              }
486
              if (isDraggingWidget) {
487
                return;
488
              }
489
              if (isDraggingFile) {
490
                this.setState({ isDraggingFile: false });
491
              }
492
            }}
493
            onDrop={(acceptedFiles, rejectedFiles) => {
494
              if (controller.workflow.state !== WORKFLOW_STATE_IDLE) {
495
                return;
496
              }
497
              if (isDraggingWidget) {
498
                return;
499
              }
500
              if (isDraggingFile) {
501
                this.setState({ isDraggingFile: false });
502
              }
503
              this.onDrop(acceptedFiles);
504
            }}
505
          >
506
            <div className={styles.workspaceTable}>
507
              <div className={styles.workspaceTableRow}>
508
                <div
509
                  ref={node => {
510
                    this.primaryContainer = node;
511
                  }}
512
                  className={classNames(
513
                    styles.primaryContainer,
514
                    { [styles.hidden]: hidePrimaryContainer }
515
                  )}
516
                >
517
                  <ButtonToolbar style={{ margin: '5px 0' }}>
518
                    <ButtonGroup
519
                      style={{ marginLeft: 0, marginRight: 10 }}
520
                      btnSize="sm"
521
                      btnStyle="flat"
522
                    >
523
                      <Button
524
                        style={{ minWidth: 30 }}
525
                        compact
526
                        onClick={this.togglePrimaryContainer}
527
                      >
528
                        <i className="fa fa-chevron-left" />
529
                      </Button>
530
                    </ButtonGroup>
531
                    <ButtonGroup
532
                      style={{ marginLeft: 0, marginRight: 10 }}
533
                      btnSize="sm"
534
                      btnStyle="flat"
535
                    >
536
                      <Button
537
                        style={{ width: 230 }}
538
                        onClick={this.updateWidgetsForPrimaryContainer}
539
                      >
540
                        <i className="fa fa-list-alt" />
541
                        {i18n._('Manage Widgets ({{inactiveCount}})', {
542
                          inactiveCount: inactiveCount
543
                        })}
544
                      </Button>
545
                    </ButtonGroup>
546
                    <ButtonGroup
547
                      style={{ marginLeft: 0, marginRight: 0 }}
548
                      btnSize="sm"
549
                      btnStyle="flat"
550
                    >
551
                      <Button
552
                        style={{ minWidth: 30 }}
553
                        compact
554
                        title={i18n._('Collapse All')}
555
                        onClick={event => {
556
                          this.primaryWidgets.collapseAll();
557
                        }}
558
                      >
559
                        <i className="fa fa-chevron-up" style={{ fontSize: 14 }} />
560
                      </Button>
561
                      <Button
562
                        style={{ minWidth: 30 }}
563
                        compact
564
                        title={i18n._('Expand All')}
565
                        onClick={event => {
566
                          this.primaryWidgets.expandAll();
567
                        }}
568
                      >
569
                        <i className="fa fa-chevron-down" style={{ fontSize: 14 }} />
570
                      </Button>
571
                    </ButtonGroup>
572
                  </ButtonToolbar>
573
                  <PrimaryWidgets
574
                    ref={node => {
575
                      this.primaryWidgets = node;
576
                    }}
577
                    onForkWidget={this.widgetEventHandler.onForkWidget}
578
                    onRemoveWidget={this.widgetEventHandler.onRemoveWidget}
579
                    onDragStart={this.widgetEventHandler.onDragStart}
580
                    onDragEnd={this.widgetEventHandler.onDragEnd}
581
                  />
582
                </div>
583
                {hidePrimaryContainer && (
584
                  <div
585
                    ref={node => {
586
                      this.primaryToggler = node;
587
                    }}
588
                    className={styles.primaryToggler}
589
                  >
590
                    <ButtonGroup
591
                      btnSize="sm"
592
                      btnStyle="flat"
593
                    >
594
                      <Button
595
                        style={{ minWidth: 30 }}
596
                        compact
597
                        onClick={this.togglePrimaryContainer}
598
                      >
599
                        <i className="fa fa-chevron-right" />
600
                      </Button>
601
                    </ButtonGroup>
602
                  </div>
603
                )}
604
                <div
605
                  ref={node => {
606
                    this.defaultContainer = node;
607
                  }}
608
                  className={classNames(
609
                    styles.defaultContainer,
610
                    styles.fixed
611
                  )}
612
                >
613
                  <DefaultWidgets />
614
                </div>
615
                {hideSecondaryContainer && (
616
                  <div
617
                    ref={node => {
618
                      this.secondaryToggler = node;
619
                    }}
620
                    className={styles.secondaryToggler}
621
                  >
622
                    <ButtonGroup
623
                      btnSize="sm"
624
                      btnStyle="flat"
625
                    >
626
                      <Button
627
                        style={{ minWidth: 30 }}
628
                        compact
629
                        onClick={this.toggleSecondaryContainer}
630
                      >
631
                        <i className="fa fa-chevron-left" />
632
                      </Button>
633
                    </ButtonGroup>
634
                  </div>
635
                )}
636
                <div
637
                  ref={node => {
638
                    this.secondaryContainer = node;
639
                  }}
640
                  className={classNames(
641
                    styles.secondaryContainer,
642
                    { [styles.hidden]: hideSecondaryContainer }
643
                  )}
644
                >
645
                  <ButtonToolbar style={{ margin: '5px 0' }}>
646
                    <div className="pull-left">
647
                      <ButtonGroup
648
                        style={{ marginLeft: 0, marginRight: 10 }}
649
                        btnSize="sm"
650
                        btnStyle="flat"
651
                      >
652
                        <Button
653
                          style={{ minWidth: 30 }}
654
                          compact
655
                          title={i18n._('Collapse All')}
656
                          onClick={event => {
657
                            this.secondaryWidgets.collapseAll();
658
                          }}
659
                        >
660
                          <i className="fa fa-chevron-up" style={{ fontSize: 14 }} />
661
                        </Button>
662
                        <Button
663
                          style={{ minWidth: 30 }}
664
                          compact
665
                          title={i18n._('Expand All')}
666
                          onClick={event => {
667
                            this.secondaryWidgets.expandAll();
668
                          }}
669
                        >
670
                          <i className="fa fa-chevron-down" style={{ fontSize: 14 }} />
671
                        </Button>
672
                      </ButtonGroup>
673
                      <ButtonGroup
674
                        style={{ marginLeft: 0, marginRight: 10 }}
675
                        btnSize="sm"
676
                        btnStyle="flat"
677
                      >
678
                        <Button
679
                          style={{ width: 230 }}
680
                          onClick={this.updateWidgetsForSecondaryContainer}
681
                        >
682
                          <i className="fa fa-list-alt" />
683
                          {i18n._('Manage Widgets ({{inactiveCount}})', {
684
                            inactiveCount: inactiveCount
685
                          })}
686
                        </Button>
687
                      </ButtonGroup>
688
                      <ButtonGroup
689
                        style={{ marginLeft: 0, marginRight: 0 }}
690
                        btnSize="sm"
691
                        btnStyle="flat"
692
                      >
693
                        <Button
694
                          style={{ minWidth: 30 }}
695
                          compact
696
                          onClick={this.toggleSecondaryContainer}
697
                        >
698
                          <i className="fa fa-chevron-right" />
699
                        </Button>
700
                      </ButtonGroup>
701
                    </div>
702
                  </ButtonToolbar>
703
                  <SecondaryWidgets
704
                    ref={node => {
705
                      this.secondaryWidgets = node;
706
                    }}
707
                    onForkWidget={this.widgetEventHandler.onForkWidget}
708
                    onRemoveWidget={this.widgetEventHandler.onRemoveWidget}
709
                    onDragStart={this.widgetEventHandler.onDragStart}
710
                    onDragEnd={this.widgetEventHandler.onDragEnd}
711
                  />
712
                </div>
713
              </div>
714
            </div>
715
          </Dropzone>
716
        </div>
717
      );
718
    }
719
}
720

721
export default withRouter(Workspace);
722

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.