cncjs

Форк
0
465 строк · 14.3 Кб
1
import classNames from 'classnames';
2
import React, { PureComponent } from 'react';
3
import { Nav, Navbar, NavDropdown, MenuItem, OverlayTrigger, Tooltip } from 'react-bootstrap';
4
import { withRouter } from 'react-router-dom';
5
import semver from 'semver';
6
import without from 'lodash/without';
7
import Push from 'push.js';
8
import api from 'app/api';
9
import Anchor from 'app/components/Anchor';
10
import Space from 'app/components/Space';
11
import settings from 'app/config/settings';
12
import combokeys from 'app/lib/combokeys';
13
import controller from 'app/lib/controller';
14
import i18n from 'app/lib/i18n';
15
import log from 'app/lib/log';
16
import * as user from 'app/lib/user';
17
import store from 'app/store';
18
import QuickAccessToolbar from './QuickAccessToolbar';
19
import styles from './index.styl';
20

21
const releases = 'https://github.com/cncjs/cncjs/releases';
22

23
const newUpdateAvailableTooltip = () => {
24
  return (
25
    <Tooltip
26
      id="navbarBrandTooltip"
27
      style={{ color: '#fff' }}
28
    >
29
      <div>{i18n._('New update available')}</div>
30
    </Tooltip>
31
  );
32
};
33

34
class Header extends PureComponent {
35
    static propTypes = {
36
      ...withRouter.propTypes
37
    };
38

39
    state = this.getInitialState();
40

41
    actions = {
42
      requestPushPermission: () => {
43
        const onGranted = () => {
44
          this.setState({ pushPermission: Push.Permission.GRANTED });
45
        };
46
        const onDenied = () => {
47
          this.setState({ pushPermission: Push.Permission.DENIED });
48
        };
49
        // Note that if "Permission.DEFAULT" is returned, no callback is executed
50
        const permission = Push.Permission.request(onGranted, onDenied);
51
        if (permission === Push.Permission.DEFAULT) {
52
          this.setState({ pushPermission: Push.Permission.DEFAULT });
53
        }
54
      },
55
      checkForUpdates: async () => {
56
        try {
57
          const res = await api.getState();
58
          const { checkForUpdates } = res.body;
59

60
          if (checkForUpdates) {
61
            const res = await api.getLatestVersion();
62
            const { time, version } = res.body;
63

64
            this._isMounted && this.setState({
65
              latestVersion: version,
66
              latestTime: time
67
            });
68
          }
69
        } catch (res) {
70
          // Ignore error
71
        }
72
      },
73
      fetchCommands: async () => {
74
        try {
75
          const res = await api.commands.fetch({ paging: false });
76
          const { records: commands } = res.body;
77

78
          this._isMounted && this.setState({
79
            commands: commands.filter(command => command.enabled)
80
          });
81
        } catch (res) {
82
          // Ignore error
83
        }
84
      },
85
      runCommand: async (cmd) => {
86
        try {
87
          const res = await api.commands.run(cmd.id);
88
          const { taskId } = res.body;
89

90
          this.setState({
91
            commands: this.state.commands.map(c => {
92
              return (c.id === cmd.id) ? { ...c, taskId: taskId, err: null } : c;
93
            })
94
          });
95
        } catch (res) {
96
          // Ignore error
97
        }
98
      }
99
    };
100

101
    actionHandlers = {
102
      CONTROLLER_COMMAND: (event, { command }) => {
103
        // feedhold, cyclestart, homing, unlock, reset
104
        controller.command(command);
105
      }
106
    };
107

108
    controllerEvents = {
109
      'config:change': () => {
110
        this.actions.fetchCommands();
111
      },
112
      'task:start': (taskId) => {
113
        this.setState({
114
          runningTasks: this.state.runningTasks.concat(taskId)
115
        });
116
      },
117
      'task:finish': (taskId, code) => {
118
        const err = (code !== 0) ? new Error(`errno=${code}`) : null;
119
        let cmd = null;
120

121
        this.setState({
122
          commands: this.state.commands.map(c => {
123
            if (c.taskId !== taskId) {
124
              return c;
125
            }
126
            cmd = c;
127
            return {
128
              ...c,
129
              taskId: null,
130
              err: err
131
            };
132
          }),
133
          runningTasks: without(this.state.runningTasks, taskId)
134
        });
135

136
        if (cmd && this.state.pushPermission === Push.Permission.GRANTED) {
137
          Push.create(cmd.title, {
138
            body: code === 0
139
              ? i18n._('Command succeeded')
140
              : i18n._('Command failed ({{err}})', { err: err }),
141
            icon: 'images/logo-badge-32x32.png',
142
            timeout: 10 * 1000,
143
            onClick: function () {
144
              window.focus();
145
              this.close();
146
            }
147
          });
148
        }
149
      },
150
      'task:error': (taskId, err) => {
151
        let cmd = null;
152

153
        this.setState({
154
          commands: this.state.commands.map(c => {
155
            if (c.taskId !== taskId) {
156
              return c;
157
            }
158
            cmd = c;
159
            return {
160
              ...c,
161
              taskId: null,
162
              err: err
163
            };
164
          }),
165
          runningTasks: without(this.state.runningTasks, taskId)
166
        });
167

168
        if (cmd && this.state.pushPermission === Push.Permission.GRANTED) {
169
          Push.create(cmd.title, {
170
            body: i18n._('Command failed ({{err}})', { err: err }),
171
            icon: 'images/logo-badge-32x32.png',
172
            timeout: 10 * 1000,
173
            onClick: function () {
174
              window.focus();
175
              this.close();
176
            }
177
          });
178
        }
179
      }
180
    };
181

182
    _isMounted = false;
183

184
    getInitialState() {
185
      let pushPermission = '';
186
      try {
187
        // Push.Permission.get() will throw an error if Push is not supported on this device
188
        pushPermission = Push.Permission.get();
189
      } catch (e) {
190
        // Ignore
191
      }
192

193
      return {
194
        pushPermission: pushPermission,
195
        commands: [],
196
        runningTasks: [],
197
        currentVersion: settings.version,
198
        latestVersion: settings.version
199
      };
200
    }
201

202
    componentDidMount() {
203
      this._isMounted = true;
204

205
      this.addActionHandlers();
206
      this.addControllerEvents();
207

208
      // Initial actions
209
      this.actions.checkForUpdates();
210
      this.actions.fetchCommands();
211
    }
212

213
    componentWillUnmount() {
214
      this._isMounted = false;
215

216
      this.removeActionHandlers();
217
      this.removeControllerEvents();
218

219
      this.runningTasks = [];
220
    }
221

222
    addActionHandlers() {
223
      Object.keys(this.actionHandlers).forEach(eventName => {
224
        const callback = this.actionHandlers[eventName];
225
        combokeys.on(eventName, callback);
226
      });
227
    }
228

229
    removeActionHandlers() {
230
      Object.keys(this.actionHandlers).forEach(eventName => {
231
        const callback = this.actionHandlers[eventName];
232
        combokeys.removeListener(eventName, callback);
233
      });
234
    }
235

236
    addControllerEvents() {
237
      Object.keys(this.controllerEvents).forEach(eventName => {
238
        const callback = this.controllerEvents[eventName];
239
        controller.addListener(eventName, callback);
240
      });
241
    }
242

243
    removeControllerEvents() {
244
      Object.keys(this.controllerEvents).forEach(eventName => {
245
        const callback = this.controllerEvents[eventName];
246
        controller.removeListener(eventName, callback);
247
      });
248
    }
249

250
    render() {
251
      const { history, location } = this.props;
252
      const { pushPermission, commands, runningTasks, currentVersion, latestVersion } = this.state;
253
      const newUpdateAvailable = semver.lt(currentVersion, latestVersion);
254
      const tooltip = newUpdateAvailable ? newUpdateAvailableTooltip() : <div />;
255
      const sessionEnabled = store.get('session.enabled');
256
      const signedInName = store.get('session.name');
257
      const hideUserDropdown = !sessionEnabled;
258
      const showCommands = commands.length > 0;
259

260
      return (
261
        <Navbar
262
          fixedTop
263
          fluid
264
          inverse
265
          style={{
266
            border: 'none',
267
            margin: 0
268
          }}
269
        >
270
          <Navbar.Header>
271
            <OverlayTrigger
272
              overlay={tooltip}
273
              placement="right"
274
            >
275
              <Anchor
276
                className="navbar-brand"
277
                style={{
278
                  padding: 0,
279
                  position: 'relative',
280
                  height: 50,
281
                  width: 60
282
                }}
283
                href={releases}
284
                target="_blank"
285
                title={`${settings.productName} ${settings.version}`}
286
              >
287
                <img
288
                  style={{
289
                    margin: '4px auto 0 auto'
290
                  }}
291
                  src="images/logo-badge-32x32.png"
292
                  alt=""
293
                />
294
                <div
295
                  style={{
296
                    fontSize: '50%',
297
                    lineHeight: '14px',
298
                    textAlign: 'center',
299
                    whiteSpace: 'nowrap',
300
                  }}
301
                >
302
                  {settings.version}
303
                </div>
304
                {newUpdateAvailable && (
305
                  <span
306
                    className="label label-primary"
307
                    style={{
308
                      fontSize: '50%',
309
                      position: 'absolute',
310
                      top: 2,
311
                      right: 2
312
                    }}
313
                  >
314
                                N
315
                  </span>
316
                )}
317
              </Anchor>
318
            </OverlayTrigger>
319
            <Navbar.Toggle />
320
          </Navbar.Header>
321
          <Navbar.Collapse>
322
            <Nav pullRight>
323
              <NavDropdown
324
                className={classNames(
325
                  { 'hidden': hideUserDropdown }
326
                )}
327
                id="nav-dropdown-user"
328
                title={(
329
                  <div title={i18n._('My Account')}>
330
                    <i className="fa fa-fw fa-user" />
331
                  </div>
332
                )}
333
                noCaret
334
              >
335
                <MenuItem header>
336
                  {i18n._('Signed in as {{name}}', { name: signedInName })}
337
                </MenuItem>
338
                <MenuItem divider />
339
                <MenuItem
340
                  href="#/settings/user-accounts"
341
                >
342
                  <i className="fa fa-fw fa-user" />
343
                  <Space width="8" />
344
                  {i18n._('Account')}
345
                </MenuItem>
346
                <MenuItem
347
                  onClick={() => {
348
                    if (user.isAuthenticated()) {
349
                      log.debug('Destroy and cleanup the WebSocket connection');
350
                      controller.disconnect();
351

352
                      user.signout();
353

354
                      // Remember current location
355
                      history.replace(location.pathname);
356
                    }
357
                  }}
358
                >
359
                  <i className="fa fa-fw fa-sign-out" />
360
                  <Space width="8" />
361
                  {i18n._('Sign Out')}
362
                </MenuItem>
363
              </NavDropdown>
364
              <NavDropdown
365
                id="nav-dropdown-menu"
366
                title={(
367
                  <div title={i18n._('Options')}>
368
                    <i className="fa fa-fw fa-ellipsis-v" />
369
                    {this.state.runningTasks.length > 0 && (
370
                      <span
371
                        className="label label-primary"
372
                        style={{
373
                          position: 'absolute',
374
                          top: 4,
375
                          right: 4
376
                        }}
377
                      >
378
                                        N
379
                      </span>
380
                    )}
381
                  </div>
382
                )}
383
                noCaret
384
              >
385
                {showCommands && (
386
                  <MenuItem header>
387
                    {i18n._('Command')}
388
                    {pushPermission === Push.Permission.GRANTED && (
389
                      <span className="pull-right">
390
                        <i className="fa fa-fw fa-bell-o" />
391
                      </span>
392
                    )}
393
                    {pushPermission === Push.Permission.DENIED && (
394
                      <span className="pull-right">
395
                        <i className="fa fa-fw fa-bell-slash-o" />
396
                      </span>
397
                    )}
398
                    {pushPermission === Push.Permission.DEFAULT && (
399
                      <span className="pull-right">
400
                        <Anchor
401
                          className={styles.btnIcon}
402
                          onClick={this.actions.requestPushPermission}
403
                          title={i18n._('Show notifications')}
404
                        >
405
                          <i className="fa fa-fw fa-bell" />
406
                        </Anchor>
407
                      </span>
408
                    )}
409
                  </MenuItem>
410
                )}
411
                {showCommands && commands.map((cmd) => {
412
                  const isTaskRunning = runningTasks.indexOf(cmd.taskId) >= 0;
413

414
                  return (
415
                    <MenuItem
416
                      key={cmd.id}
417
                      disabled={cmd.disabled}
418
                      onSelect={() => {
419
                        this.actions.runCommand(cmd);
420
                      }}
421
                    >
422
                      <span title={cmd.command}>{cmd.title || cmd.command}</span>
423
                      <span className="pull-right">
424
                        <i
425
                          className={classNames(
426
                            'fa',
427
                            'fa-fw',
428
                            { 'fa-circle-o-notch': isTaskRunning },
429
                            { 'fa-spin': isTaskRunning },
430
                            { 'fa-exclamation-circle': cmd.err },
431
                            { 'text-error': cmd.err }
432
                          )}
433
                          title={cmd.err}
434
                        />
435
                      </span>
436
                    </MenuItem>
437
                  );
438
                })}
439
                {showCommands &&
440
                  <MenuItem divider />
441
                }
442
                <MenuItem
443
                  href="https://github.com/cncjs/cncjs/wiki"
444
                  target="_blank"
445
                >
446
                  {i18n._('Help')}
447
                </MenuItem>
448
                <MenuItem
449
                  href="https://github.com/cncjs/cncjs/issues"
450
                  target="_blank"
451
                >
452
                  {i18n._('Report an issue')}
453
                </MenuItem>
454
              </NavDropdown>
455
            </Nav>
456
            {location.pathname === '/workspace' &&
457
              <QuickAccessToolbar state={this.state} actions={this.actions} />
458
            }
459
          </Navbar.Collapse>
460
        </Navbar>
461
      );
462
    }
463
}
464

465
export default withRouter(Header);
466

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

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

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

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