@jetbrains/ring-ui 4.1.7 → 4.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -516,19 +516,17 @@ describe('Auth', () => {
516
516
  }
517
517
  });
518
518
 
519
- it('should show login overlay if token refresh fails and window login enabled', async () => {
519
+ it('should show login overlay if token refresh fails and window login enabled', done => {
520
520
  auth._backgroundFlow._timeout = 100;
521
521
  sandbox.stub(BackgroundFlow.prototype, '_redirectFrame');
522
522
  sandbox.stub(Auth.prototype, '_showAuthDialog');
523
523
 
524
- try {
525
- await auth.requestToken();
526
- } catch (reject) {
527
- Auth.prototype._showAuthDialog.should.have.been.calledWith({
528
- nonInteractive: true,
529
- error: reject
530
- });
531
- }
524
+ auth.requestToken();
525
+
526
+ setTimeout(() => {
527
+ Auth.prototype._showAuthDialog.should.have.been.called;
528
+ done();
529
+ }, auth._backgroundFlow._timeout * 2);
532
530
  });
533
531
  });
534
532
 
@@ -52,6 +52,7 @@ const DEFAULT_CONFIG = {
52
52
  login: 'Log in',
53
53
  loginTo: 'Log in to %serviceName%',
54
54
  cancel: 'Cancel',
55
+ tryAgainLabel: 'Try again',
55
56
  postpone: 'Postpone',
56
57
  youHaveLoggedInAs: 'You have logged in as another user: %userName%',
57
58
  applyChange: 'Apply change',
@@ -405,9 +406,27 @@ export default class Auth {
405
406
  try {
406
407
  return await this._backgroundFlow.authorize();
407
408
  } catch (error) {
408
-
409
409
  if (this._canShowDialogs()) {
410
- return this._showAuthDialog({nonInteractive: true, error});
410
+ return new Promise(resolve => {
411
+ const onTryAgain = async () => {
412
+ try {
413
+ const result = await this._backgroundFlow.authorize();
414
+ resolve(result);
415
+ } catch (retryError) {
416
+ this._showAuthDialog({
417
+ nonInteractive: true,
418
+ error: retryError,
419
+ onTryAgain
420
+ });
421
+ throw retryError;
422
+ }
423
+ };
424
+ this._showAuthDialog({
425
+ nonInteractive: true,
426
+ error,
427
+ onTryAgain
428
+ });
429
+ });
411
430
  } else {
412
431
  const authRequest = await this._requestBuilder.prepareAuthRequest();
413
432
  this._redirectCurrentPage(authRequest.url);
@@ -516,7 +535,7 @@ export default class Auth {
516
535
  this.logout();
517
536
  }
518
537
 
519
- _showAuthDialog({nonInteractive, error, canCancel} = {}) {
538
+ _showAuthDialog({nonInteractive, error, canCancel, onTryAgain} = {}) {
520
539
  const {embeddedLogin, onPostponeLogout, translations} = this.config;
521
540
  const cancelable = this.user.guest || canCancel;
522
541
 
@@ -557,15 +576,22 @@ export default class Auth {
557
576
  }
558
577
  };
559
578
 
579
+ const onTryAgainClick = async () => {
580
+ await onTryAgain();
581
+ closeDialog();
582
+ };
583
+
560
584
  const hide = this._authDialogService({
561
585
  ...this._service,
562
586
  loginCaption: translations.login,
563
587
  loginToCaption: translations.loginTo,
564
588
  confirmLabel: translations.login,
589
+ tryAgainLabel: translations.tryAgainLabel,
565
590
  cancelLabel: cancelable ? translations.cancel : translations.postpone,
566
591
  errorMessage: this._extractErrorMessage(error, true),
567
592
  onConfirm,
568
- onCancel
593
+ onCancel,
594
+ onTryAgain: onTryAgain ? onTryAgainClick : null
569
595
  });
570
596
 
571
597
  const stopTokenListening = this._storage.onTokenChange(token => {
@@ -1,14 +1,13 @@
1
1
  import React from 'react';
2
2
 
3
+ import youtrackLogo from '!file-loader!@jetbrains/logos/youtrack/youtrack.svg';
4
+
3
5
  import reactDecorator from '../../.storybook/react-decorator';
4
6
 
5
7
  import Button from '@jetbrains/ring-ui/components/button/button';
6
8
 
7
9
  import AuthDialog from '@jetbrains/ring-ui/components/auth-dialog/auth-dialog';
8
10
 
9
- import youtrackLogo from '!file-loader!@jetbrains/logos/youtrack/youtrack.svg';
10
-
11
-
12
11
  export default {
13
12
  title: 'Components/Auth Dialog',
14
13
  decorators: [reactDecorator()],
@@ -28,9 +28,11 @@ export default class AuthDialog extends Component {
28
28
  cancelOnEsc: PropTypes.bool,
29
29
  confirmLabel: PropTypes.string,
30
30
  cancelLabel: PropTypes.string,
31
+ tryAgainLabel: PropTypes.string,
31
32
 
32
33
  onConfirm: PropTypes.func,
33
- onCancel: PropTypes.func
34
+ onCancel: PropTypes.func,
35
+ onTryAgain: PropTypes.func
34
36
  };
35
37
 
36
38
  static defaultProps = {
@@ -44,12 +46,38 @@ export default class AuthDialog extends Component {
44
46
  onCancel: () => {}
45
47
  };
46
48
 
49
+ state = {
50
+ retrying: false
51
+ };
52
+
53
+ componentDidMount() {
54
+ window.addEventListener('online', this.onRetryPress);
55
+ }
56
+
57
+ componentWillUnmount() {
58
+ window.removeEventListener('online', this.onRetryPress);
59
+ }
60
+
47
61
  onEscPress = () => {
48
62
  if (this.props.cancelOnEsc) {
49
63
  this.props.onCancel();
50
64
  }
51
65
  };
52
66
 
67
+ onRetryPress = async () => {
68
+ if (!this.props.onTryAgain || this.state.retrying) {
69
+ return;
70
+ }
71
+ this.setState({retrying: true});
72
+ try {
73
+ await this.props.onTryAgain();
74
+ } catch (e) {
75
+ // do nothing, error is handled in onTryAgain
76
+ } finally {
77
+ this.setState({retrying: false});
78
+ }
79
+ };
80
+
53
81
  render() {
54
82
  const {
55
83
  show,
@@ -61,10 +89,14 @@ export default class AuthDialog extends Component {
61
89
  loginToCaption,
62
90
  confirmLabel,
63
91
  cancelLabel,
92
+ tryAgainLabel,
64
93
  onConfirm,
65
- onCancel
94
+ onCancel,
95
+ onTryAgain
66
96
  } = this.props;
67
97
 
98
+ const {retrying} = this.state;
99
+
68
100
  const defaultTitle = serviceName ? loginToCaption : loginCaption;
69
101
  const title = (this.props.title || defaultTitle).replace('%serviceName%', serviceName);
70
102
 
@@ -99,6 +131,17 @@ export default class AuthDialog extends Component {
99
131
  >
100
132
  {confirmLabel}
101
133
  </Button>
134
+ {onTryAgain && (
135
+ <Button
136
+ className={styles.button}
137
+ data-test="auth-dialog-retry-button"
138
+ onClick={() => this.onRetryPress()}
139
+ loader={retrying}
140
+ disabled={retrying}
141
+ >
142
+ {tryAgainLabel}
143
+ </Button>
144
+ )}
102
145
  <Button
103
146
  className={styles.button}
104
147
  data-test="auth-dialog-cancel-button"
@@ -727,9 +727,9 @@ export default class Select extends Component {
727
727
  return isInputMode(this.props.type);
728
728
  }
729
729
 
730
- _clickHandler = (forceShowPopup = false) => {
730
+ _clickHandler = () => {
731
731
  if (!this.props.disabled) {
732
- if (this.state.showPopup && !forceShowPopup) {
732
+ if (this.state.showPopup) {
733
733
  this._hidePopup();
734
734
  } else {
735
735
  this.props.onBeforeOpen();
@@ -738,6 +738,14 @@ export default class Select extends Component {
738
738
  }
739
739
  };
740
740
 
741
+ _openPopupIfClosed = () => {
742
+ if (this.props.disabled || this.state.showPopup) {
743
+ return;
744
+ }
745
+ this.props.onBeforeOpen();
746
+ this._showPopup();
747
+ };
748
+
741
749
  _filterChangeHandler = e => {
742
750
  this._setFilter(e.target.value, e);
743
751
  };
@@ -38,11 +38,11 @@ class SelectLazy {
38
38
  }
39
39
 
40
40
  attachEvents() {
41
- this.container.addEventListener('click', this.onClick);
41
+ this.container.addEventListener('click', this.onClick, {capture: true});
42
42
  }
43
43
 
44
44
  detachEvents() {
45
- this.container.removeEventListener('click', this.onClick);
45
+ this.container.removeEventListener('click', this.onClick, {capture: true});
46
46
  }
47
47
 
48
48
  render(props) {
@@ -59,11 +59,9 @@ class SelectLazy {
59
59
  this.detachEvents();
60
60
  if (this.type === 'dropdown') {
61
61
  this.ctrl.selectInstance = render(this.reactSelect, this.container);
62
- // In "dropdown" mode we don't click select itself, so need to force click handler
63
- this.ctrl.selectInstance._clickHandler();
62
+ this.ctrl.selectInstance._openPopupIfClosed();
64
63
  } else {
65
64
  this.ctrl.selectInstance = hydrate(this.reactSelect, this.container);
66
- this.ctrl.selectInstance._clickHandler(true);
67
65
  }
68
66
  }
69
67
  }
@@ -86,6 +86,7 @@ const DEFAULT_CONFIG = {
86
86
  login: 'Log in',
87
87
  loginTo: 'Log in to %serviceName%',
88
88
  cancel: 'Cancel',
89
+ tryAgainLabel: 'Try again',
89
90
  postpone: 'Postpone',
90
91
  youHaveLoggedInAs: 'You have logged in as another user: %userName%',
91
92
  applyChange: 'Apply change',
@@ -475,9 +476,27 @@ class Auth {
475
476
  return await this._backgroundFlow.authorize();
476
477
  } catch (error) {
477
478
  if (this._canShowDialogs()) {
478
- return this._showAuthDialog({
479
- nonInteractive: true,
480
- error
479
+ return new Promise(resolve => {
480
+ const onTryAgain = async () => {
481
+ try {
482
+ const result = await this._backgroundFlow.authorize();
483
+ resolve(result);
484
+ } catch (retryError) {
485
+ this._showAuthDialog({
486
+ nonInteractive: true,
487
+ error: retryError,
488
+ onTryAgain
489
+ });
490
+
491
+ throw retryError;
492
+ }
493
+ };
494
+
495
+ this._showAuthDialog({
496
+ nonInteractive: true,
497
+ error,
498
+ onTryAgain
499
+ });
481
500
  });
482
501
  } else {
483
502
  const authRequest = await this._requestBuilder.prepareAuthRequest();
@@ -603,7 +622,8 @@ class Auth {
603
622
  let {
604
623
  nonInteractive,
605
624
  error,
606
- canCancel
625
+ canCancel,
626
+ onTryAgain
607
627
  } = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
608
628
  const {
609
629
  embeddedLogin,
@@ -654,14 +674,21 @@ class Auth {
654
674
  }
655
675
  };
656
676
 
677
+ const onTryAgainClick = async () => {
678
+ await onTryAgain();
679
+ closeDialog();
680
+ };
681
+
657
682
  const hide = this._authDialogService({ ...this._service,
658
683
  loginCaption: translations.login,
659
684
  loginToCaption: translations.loginTo,
660
685
  confirmLabel: translations.login,
686
+ tryAgainLabel: translations.tryAgainLabel,
661
687
  cancelLabel: cancelable ? translations.cancel : translations.postpone,
662
688
  errorMessage: this._extractErrorMessage(error, true),
663
689
  onConfirm,
664
- onCancel
690
+ onCancel,
691
+ onTryAgain: onTryAgain ? onTryAgainClick : null
665
692
  });
666
693
 
667
694
  const stopTokenListening = this._storage.onTokenChange(token => {
@@ -49,11 +49,42 @@ class AuthDialog extends Component {
49
49
  constructor() {
50
50
  super(...arguments);
51
51
 
52
+ _defineProperty(this, "state", {
53
+ retrying: false
54
+ });
55
+
52
56
  _defineProperty(this, "onEscPress", () => {
53
57
  if (this.props.cancelOnEsc) {
54
58
  this.props.onCancel();
55
59
  }
56
60
  });
61
+
62
+ _defineProperty(this, "onRetryPress", async () => {
63
+ if (!this.props.onTryAgain || this.state.retrying) {
64
+ return;
65
+ }
66
+
67
+ this.setState({
68
+ retrying: true
69
+ });
70
+
71
+ try {
72
+ await this.props.onTryAgain();
73
+ } catch (e) {// do nothing, error is handled in onTryAgain
74
+ } finally {
75
+ this.setState({
76
+ retrying: false
77
+ });
78
+ }
79
+ });
80
+ }
81
+
82
+ componentDidMount() {
83
+ window.addEventListener('online', this.onRetryPress);
84
+ }
85
+
86
+ componentWillUnmount() {
87
+ window.removeEventListener('online', this.onRetryPress);
57
88
  }
58
89
 
59
90
  render() {
@@ -67,9 +98,14 @@ class AuthDialog extends Component {
67
98
  loginToCaption,
68
99
  confirmLabel,
69
100
  cancelLabel,
101
+ tryAgainLabel,
70
102
  onConfirm,
71
- onCancel
103
+ onCancel,
104
+ onTryAgain
72
105
  } = this.props;
106
+ const {
107
+ retrying
108
+ } = this.state;
73
109
  const defaultTitle = serviceName ? loginToCaption : loginCaption;
74
110
  const title = (this.props.title || defaultTitle).replace('%serviceName%', serviceName);
75
111
  return /*#__PURE__*/React.createElement(Dialog, {
@@ -95,7 +131,13 @@ class AuthDialog extends Component {
95
131
  className: modules_ae521deb.firstButton,
96
132
  "data-test": "auth-dialog-confirm-button",
97
133
  onClick: onConfirm
98
- }, confirmLabel), /*#__PURE__*/React.createElement(Button, {
134
+ }, confirmLabel), onTryAgain && /*#__PURE__*/React.createElement(Button, {
135
+ className: modules_ae521deb.button,
136
+ "data-test": "auth-dialog-retry-button",
137
+ onClick: () => this.onRetryPress(),
138
+ loader: retrying,
139
+ disabled: retrying
140
+ }, tryAgainLabel), /*#__PURE__*/React.createElement(Button, {
99
141
  className: modules_ae521deb.button,
100
142
  "data-test": "auth-dialog-cancel-button",
101
143
  onClick: onCancel
@@ -116,8 +158,10 @@ _defineProperty(AuthDialog, "propTypes", {
116
158
  cancelOnEsc: PropTypes.bool,
117
159
  confirmLabel: PropTypes.string,
118
160
  cancelLabel: PropTypes.string,
161
+ tryAgainLabel: PropTypes.string,
119
162
  onConfirm: PropTypes.func,
120
- onCancel: PropTypes.func
163
+ onCancel: PropTypes.func,
164
+ onTryAgain: PropTypes.func
121
165
  });
122
166
 
123
167
  _defineProperty(AuthDialog, "defaultProps", {
@@ -379,20 +379,28 @@ class Select extends Component {
379
379
 
380
380
  _defineProperty(this, "doesLabelMatch", doesLabelMatch);
381
381
 
382
- _defineProperty(this, "_clickHandler", function () {
383
- let forceShowPopup = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
384
-
385
- if (!_this.props.disabled) {
386
- if (_this.state.showPopup && !forceShowPopup) {
387
- _this._hidePopup();
382
+ _defineProperty(this, "_clickHandler", () => {
383
+ if (!this.props.disabled) {
384
+ if (this.state.showPopup) {
385
+ this._hidePopup();
388
386
  } else {
389
- _this.props.onBeforeOpen();
387
+ this.props.onBeforeOpen();
390
388
 
391
- _this._showPopup();
389
+ this._showPopup();
392
390
  }
393
391
  }
394
392
  });
395
393
 
394
+ _defineProperty(this, "_openPopupIfClosed", () => {
395
+ if (this.props.disabled || this.state.showPopup) {
396
+ return;
397
+ }
398
+
399
+ this.props.onBeforeOpen();
400
+
401
+ this._showPopup();
402
+ });
403
+
396
404
  _defineProperty(this, "_filterChangeHandler", e => {
397
405
  this._setFilter(e.target.value, e);
398
406
  });
@@ -115,11 +115,15 @@ class SelectLazy {
115
115
  }
116
116
 
117
117
  attachEvents() {
118
- this.container.addEventListener('click', this.onClick);
118
+ this.container.addEventListener('click', this.onClick, {
119
+ capture: true
120
+ });
119
121
  }
120
122
 
121
123
  detachEvents() {
122
- this.container.removeEventListener('click', this.onClick);
124
+ this.container.removeEventListener('click', this.onClick, {
125
+ capture: true
126
+ });
123
127
  }
124
128
 
125
129
  render(props) {
@@ -137,13 +141,11 @@ class SelectLazy {
137
141
  this.detachEvents();
138
142
 
139
143
  if (this.type === 'dropdown') {
140
- this.ctrl.selectInstance = render(this.reactSelect, this.container); // In "dropdown" mode we don't click select itself, so need to force click handler
144
+ this.ctrl.selectInstance = render(this.reactSelect, this.container);
141
145
 
142
- this.ctrl.selectInstance._clickHandler();
146
+ this.ctrl.selectInstance._openPopupIfClosed();
143
147
  } else {
144
148
  this.ctrl.selectInstance = hydrate(this.reactSelect, this.container);
145
-
146
- this.ctrl.selectInstance._clickHandler(true);
147
149
  }
148
150
  }
149
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetbrains/ring-ui",
3
- "version": "4.1.7",
3
+ "version": "4.1.11",
4
4
  "description": "JetBrains UI library",
5
5
  "author": "JetBrains",
6
6
  "license": "Apache-2.0",
@@ -70,25 +70,25 @@
70
70
  "@babel/cli": "^7.16.7",
71
71
  "@babel/eslint-parser": "^7.16.5",
72
72
  "@jetbrains/eslint-config": "^5.3.1",
73
- "@jetbrains/generator-ring-ui": "^4.1.3",
73
+ "@jetbrains/generator-ring-ui": "^4.1.4",
74
74
  "@jetbrains/stylelint-config": "^3.0.2",
75
75
  "@primer/octicons": "^16.2.0",
76
76
  "@rollup/plugin-babel": "^5.3.0",
77
77
  "@rollup/plugin-replace": "^3.0.1",
78
- "@storybook/addon-a11y": "6.4.9",
79
- "@storybook/addon-docs": "6.4.9",
80
- "@storybook/addon-essentials": "6.4.9",
78
+ "@storybook/addon-a11y": "6.4.10",
79
+ "@storybook/addon-docs": "6.4.10",
80
+ "@storybook/addon-essentials": "6.4.10",
81
81
  "@storybook/addon-storyshots": "6.4.3",
82
82
  "@storybook/addon-storyshots-puppeteer": "6.4.3",
83
83
  "@storybook/addon-storysource": "6.4.9",
84
84
  "@storybook/addons": "6.4.9",
85
- "@storybook/builder-webpack5": "6.4.9",
86
- "@storybook/client-api": "6.4.9",
85
+ "@storybook/builder-webpack5": "6.4.10",
86
+ "@storybook/client-api": "6.4.12",
87
87
  "@storybook/core": "6.4.9",
88
- "@storybook/html": "6.4.9",
89
- "@storybook/manager-webpack5": "^6.4.9",
90
- "@storybook/source-loader": "6.4.9",
91
- "@storybook/theming": "6.4.9",
88
+ "@storybook/html": "6.4.10",
89
+ "@storybook/manager-webpack5": "^6.4.10",
90
+ "@storybook/source-loader": "6.4.10",
91
+ "@storybook/theming": "6.4.12",
92
92
  "@testing-library/react": "^12.1.2",
93
93
  "@testing-library/user-event": "^13.5.0",
94
94
  "@wojtekmaj/enzyme-adapter-react-17": "^0.6.6",
@@ -108,7 +108,7 @@
108
108
  "eslint-import-resolver-webpack": "^0.13.2",
109
109
  "eslint-plugin-angular": "^4.1.0",
110
110
  "eslint-plugin-bdd": "^2.1.1",
111
- "eslint-plugin-import": "^2.25.3",
111
+ "eslint-plugin-import": "^2.25.4",
112
112
  "eslint-plugin-jsx-a11y": "^6.5.1",
113
113
  "eslint-plugin-react": "^7.28.0",
114
114
  "events": "^3.3.0",
@@ -117,7 +117,7 @@
117
117
  "husky": "^7.0.4",
118
118
  "identity-obj-proxy": "^3.0.0",
119
119
  "imports-loader": "^3.1.1",
120
- "jest": "~27.4.5",
120
+ "jest": "~27.4.7",
121
121
  "jest-teamcity": "^1.10.0",
122
122
  "karma": "^6.3.9",
123
123
  "karma-chrome-launcher": "3.1.0",
@@ -126,7 +126,7 @@
126
126
  "karma-teamcity-reporter": "^2.0.0",
127
127
  "karma-webpack": "^5.0.0",
128
128
  "lerna": "^4.0.0",
129
- "lint-staged": "^12.1.5",
129
+ "lint-staged": "^12.1.6",
130
130
  "merge-options": "^3.0.4",
131
131
  "mocha": "^9.1.3",
132
132
  "pinst": "^2.1.6",
@@ -191,7 +191,7 @@
191
191
  "focus-trap": "^6.7.1",
192
192
  "focus-visible": "^5.2.0",
193
193
  "highlight.js": "^10.7.2",
194
- "html-loader": "^3.0.1",
194
+ "html-loader": "^3.1.0",
195
195
  "interpolate-loader": "^2.0.1",
196
196
  "just-debounce-it": "^3.0.1",
197
197
  "memoize-one": "^6.0.0",
@@ -202,7 +202,7 @@
202
202
  "postcss-loader": "^6.2.1",
203
203
  "postcss-modules-values-replace": "^3.4.0",
204
204
  "postcss-preset-env": "^7.2.0",
205
- "prop-types": "^15.8.0",
205
+ "prop-types": "^15.8.1",
206
206
  "react-markdown": "^5.0.3",
207
207
  "react-movable": "^3.0.2",
208
208
  "react-virtualized": "^9.22.3",
@@ -221,5 +221,5 @@
221
221
  "node": ">=7.4",
222
222
  "npm": ">=6.0.0"
223
223
  },
224
- "gitHead": "882b425327661b41b8234af5416e4a7db850addd"
224
+ "gitHead": "9a657099ffa39aee3526ec1174f2eddeed034111"
225
225
  }