@dhis2-ui/header-bar 8.4.17 → 8.5.0-beta.2
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.
- package/build/cjs/__e2e__/header-bar.stories.e2e.js +40 -0
- package/build/cjs/__e2e__/stories/common.js +99 -32
- package/build/cjs/__e2e__/stories/with-debug-info-edge-cases.js +45 -0
- package/build/cjs/__e2e__/stories/with-update-available-notification.js +38 -0
- package/build/cjs/apps.js +1 -1
- package/build/cjs/debug-info/debug-info-menu-item.js +67 -0
- package/build/cjs/debug-info/debug-info-modal.js +59 -0
- package/build/cjs/debug-info/debug-info-table.js +47 -0
- package/build/cjs/debug-info/use-debug-info.js +28 -0
- package/build/cjs/features/the_headerbar_should_display_app_update_notification/index.js +34 -0
- package/build/cjs/features/the_headerbar_should_display_app_update_notification.feature +22 -0
- package/build/cjs/features/the_headerbar_should_display_debug_version_infos/index.js +69 -0
- package/build/cjs/features/the_headerbar_should_display_debug_version_infos.feature +52 -0
- package/build/cjs/header-bar-context.js +46 -0
- package/build/cjs/header-bar.js +22 -11
- package/build/cjs/header-bar.stories.js +70 -72
- package/build/cjs/locales/en/translations.json +12 -1
- package/build/cjs/profile/use-on-doc-click.js +30 -0
- package/build/cjs/profile/use-on-doc-click.test.js +42 -0
- package/build/cjs/profile-menu/index.js +18 -0
- package/build/cjs/{profile → profile-menu}/profile-header.js +0 -0
- package/build/cjs/{profile → profile-menu}/profile-menu.js +23 -15
- package/build/cjs/profile-menu/update-notification.js +70 -0
- package/build/cjs/profile.js +52 -62
- package/build/es/__e2e__/header-bar.stories.e2e.js +5 -3
- package/build/es/__e2e__/stories/common.js +96 -33
- package/build/es/__e2e__/stories/with-debug-info-edge-cases.js +22 -0
- package/build/es/__e2e__/stories/with-update-available-notification.js +19 -0
- package/build/es/apps.js +1 -1
- package/build/es/debug-info/debug-info-menu-item.js +48 -0
- package/build/es/debug-info/debug-info-modal.js +41 -0
- package/build/es/debug-info/debug-info-table.js +35 -0
- package/build/es/debug-info/use-debug-info.js +15 -0
- package/build/es/features/the_headerbar_should_display_app_update_notification/index.js +31 -0
- package/build/es/features/the_headerbar_should_display_app_update_notification.feature +22 -0
- package/build/es/features/the_headerbar_should_display_debug_version_infos/index.js +66 -0
- package/build/es/features/the_headerbar_should_display_debug_version_infos.feature +52 -0
- package/build/es/header-bar-context.js +25 -0
- package/build/es/header-bar.js +21 -11
- package/build/es/header-bar.stories.js +65 -71
- package/build/es/locales/en/translations.json +12 -1
- package/build/es/profile/use-on-doc-click.js +20 -0
- package/build/es/profile/use-on-doc-click.test.js +38 -0
- package/build/es/profile-menu/index.js +1 -0
- package/build/es/{profile → profile-menu}/profile-header.js +0 -0
- package/build/es/{profile → profile-menu}/profile-menu.js +23 -17
- package/build/es/profile-menu/update-notification.js +51 -0
- package/build/es/profile.js +49 -64
- package/package.json +15 -13
- package/build/cjs/features/the_headerbar_displays_instance_and_app_infos/index.js +0 -15
- package/build/cjs/features/the_headerbar_displays_instance_and_app_infos.feature +0 -29
- package/build/es/features/the_headerbar_displays_instance_and_app_infos/index.js +0 -12
- package/build/es/features/the_headerbar_displays_instance_and_app_infos.feature +0 -29
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
|
|
2
|
+
Given('the HeaderBar is rendered without an instance version in runtime context', () => {
|
|
3
|
+
cy.visitStory('HeaderBarTesting', 'With Unknown Instance Version');
|
|
4
|
+
});
|
|
5
|
+
Given('the HeaderBar is rendered with an app name and app version in runtime context', () => {
|
|
6
|
+
cy.visitStory('HeaderBarTesting', 'default');
|
|
7
|
+
});
|
|
8
|
+
Given('the HeaderBar is rendered without app name in runtime context', () => {
|
|
9
|
+
cy.visitStory('HeaderBarTesting', 'With Unknown App Name');
|
|
10
|
+
});
|
|
11
|
+
Given('the HeaderBar is rendered with an app name but without app version in runtime context', () => {
|
|
12
|
+
cy.visitStory('HeaderBarTesting', 'With Unknown App Version');
|
|
13
|
+
});
|
|
14
|
+
Given('the HeaderBar is rendered without app name or app version in runtime context', () => {
|
|
15
|
+
cy.visitStory('HeaderBarTesting', 'With Unknown App Name And Version');
|
|
16
|
+
});
|
|
17
|
+
When('the user opens the profile menu', () => {
|
|
18
|
+
cy.get('[data-test="headerbar-profile"] > button').click();
|
|
19
|
+
});
|
|
20
|
+
Then("the app's name and version should be displayed", () => {
|
|
21
|
+
cy.get('[data-test="dhis2-ui-headerbar-appinfo"]').should('contain', 'TestApp 101.2.3-beta.4');
|
|
22
|
+
});
|
|
23
|
+
Then("the app's name with unknown version should be displayed", () => {
|
|
24
|
+
cy.get('[data-test="dhis2-ui-headerbar-appinfo"]').should('contain', 'TestApp version unknown');
|
|
25
|
+
});
|
|
26
|
+
Then("the unknown app with app's version should be displayed", () => {
|
|
27
|
+
cy.get('[data-test="dhis2-ui-headerbar-appinfo"]').should('contain', 'App 101.2.3-beta.4');
|
|
28
|
+
});
|
|
29
|
+
Then('the unknown app with unknown version should be displayed', () => {
|
|
30
|
+
cy.get('[data-test="dhis2-ui-headerbar-appinfo"]').should('contain', 'App version unknown');
|
|
31
|
+
});
|
|
32
|
+
Then('the instance version should be displayed', () => {
|
|
33
|
+
cy.get('[data-test="dhis2-ui-headerbar-instanceinfo"]').should('contain', 'DHIS2 2.39.2.1-SNAPSHOT');
|
|
34
|
+
});
|
|
35
|
+
Then('the instance version should show as unknown', () => {
|
|
36
|
+
cy.get('[data-test="dhis2-ui-headerbar-instanceinfo"]').should('contain', 'DHIS2 version unknown');
|
|
37
|
+
});
|
|
38
|
+
When('the user clicks the debug info menu item', () => {
|
|
39
|
+
cy.get('[data-test="dhis2-ui-headerbar-debuginfo"] > a').click();
|
|
40
|
+
});
|
|
41
|
+
Then('the debug info modal should be shown', () => {
|
|
42
|
+
cy.get('[data-test="dhis2-ui-headerbar-debuginfomodal"]').should('be.visible');
|
|
43
|
+
});
|
|
44
|
+
Then('the debug info modal should not be shown', () => {
|
|
45
|
+
cy.get('[data-test="dhis2-ui-headerbar-debuginfomodal"]').should('not.exist');
|
|
46
|
+
});
|
|
47
|
+
Then('the debug info modal should contain debug info', () => {
|
|
48
|
+
cy.get('[data-test="dhis2-ui-headerbar-debuginfotable"]').should('contain', '2.39.2.1-SNAPSHOT' // DHIS2 version
|
|
49
|
+
).should('contain', '6607c3c' // Revision
|
|
50
|
+
).should('contain', 'TestApp' // App name
|
|
51
|
+
).should('contain', '101.2.3-beta.4' // App version
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
When('the user clicks the copy debug info button', () => {
|
|
55
|
+
cy.contains('Copy debug info').click();
|
|
56
|
+
});
|
|
57
|
+
Then('the debug info should be copied to clipboard', () => {
|
|
58
|
+
cy.window().then(win => {
|
|
59
|
+
win.navigator.clipboard.readText().then(text => {
|
|
60
|
+
expect(text).to.contain('2.39.2.1-SNAPSHOT');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
Then('the debug info copied to clipboard alert should be shown', () => {
|
|
65
|
+
cy.contains('Debug information copied to clipboard').should('exist');
|
|
66
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Feature: The HeaderBar should display debug version infos
|
|
2
|
+
|
|
3
|
+
Scenario: The debug version infos are displayed in the profile menu
|
|
4
|
+
Given the HeaderBar is rendered with an app name and app version in runtime context
|
|
5
|
+
When the user opens the profile menu
|
|
6
|
+
Then the instance version should be displayed
|
|
7
|
+
And the app's name and version should be displayed
|
|
8
|
+
|
|
9
|
+
Scenario: The debug version info modal is displayed when clicking on the menu item
|
|
10
|
+
Given the HeaderBar is rendered with an app name and app version in runtime context
|
|
11
|
+
When the user opens the profile menu
|
|
12
|
+
When the user clicks the debug info menu item
|
|
13
|
+
Then the debug info modal should be shown
|
|
14
|
+
|
|
15
|
+
Scenario: The debug version info modal displays debug info
|
|
16
|
+
Given the HeaderBar is rendered with an app name and app version in runtime context
|
|
17
|
+
When the user opens the profile menu
|
|
18
|
+
When the user clicks the debug info menu item
|
|
19
|
+
Then the debug info modal should contain debug info
|
|
20
|
+
|
|
21
|
+
Scenario: The debug version info should be copied to clipboard
|
|
22
|
+
Given the HeaderBar is rendered with an app name and app version in runtime context
|
|
23
|
+
When the user opens the profile menu
|
|
24
|
+
When the user clicks the debug info menu item
|
|
25
|
+
When the user clicks the copy debug info button
|
|
26
|
+
Then the debug info should be copied to clipboard
|
|
27
|
+
And the debug info copied to clipboard alert should be shown
|
|
28
|
+
And the debug info modal should not be shown
|
|
29
|
+
|
|
30
|
+
Scenario: The debug version infos are displayed with unknown dhis2 version in the profile menu
|
|
31
|
+
Given the HeaderBar is rendered without an instance version in runtime context
|
|
32
|
+
When the user opens the profile menu
|
|
33
|
+
Then the instance version should show as unknown
|
|
34
|
+
And the app's name and version should be displayed
|
|
35
|
+
|
|
36
|
+
Scenario: The debug version infos are displayed with unknown app name and version in the profile menu
|
|
37
|
+
Given the HeaderBar is rendered without app name or app version in runtime context
|
|
38
|
+
When the user opens the profile menu
|
|
39
|
+
Then the instance version should be displayed
|
|
40
|
+
And the unknown app with unknown version should be displayed
|
|
41
|
+
|
|
42
|
+
Scenario: The debug version infos are displayed with unknown app name in the profile menu
|
|
43
|
+
Given the HeaderBar is rendered without app name in runtime context
|
|
44
|
+
When the user opens the profile menu
|
|
45
|
+
Then the instance version should be displayed
|
|
46
|
+
And the unknown app with app's version should be displayed
|
|
47
|
+
|
|
48
|
+
Scenario: The debug version infos are displayed with unknown app version in the profile menu
|
|
49
|
+
Given the HeaderBar is rendered with an app name but without app version in runtime context
|
|
50
|
+
When the user opens the profile menu
|
|
51
|
+
Then the instance version should be displayed
|
|
52
|
+
And the app's name with unknown version should be displayed
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import PropTypes from 'prop-types';
|
|
2
|
+
import React, { createContext, useContext } from 'react';
|
|
3
|
+
const headerBarContext = /*#__PURE__*/createContext({
|
|
4
|
+
updateAvailable: false,
|
|
5
|
+
onApplyAvailableUpdate: () => {}
|
|
6
|
+
});
|
|
7
|
+
export const HeaderBarContextProvider = _ref => {
|
|
8
|
+
let {
|
|
9
|
+
updateAvailable,
|
|
10
|
+
onApplyAvailableUpdate,
|
|
11
|
+
children
|
|
12
|
+
} = _ref;
|
|
13
|
+
return /*#__PURE__*/React.createElement(headerBarContext.Provider, {
|
|
14
|
+
value: {
|
|
15
|
+
updateAvailable,
|
|
16
|
+
onApplyAvailableUpdate
|
|
17
|
+
}
|
|
18
|
+
}, children);
|
|
19
|
+
};
|
|
20
|
+
HeaderBarContextProvider.propTypes = {
|
|
21
|
+
children: PropTypes.node,
|
|
22
|
+
updateAvailable: PropTypes.bool,
|
|
23
|
+
onApplyAvailableUpdate: PropTypes.func
|
|
24
|
+
};
|
|
25
|
+
export const useHeaderBarContext = () => useContext(headerBarContext);
|
package/build/es/header-bar.js
CHANGED
|
@@ -4,6 +4,7 @@ import { colors } from '@dhis2/ui-constants';
|
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
import Apps from './apps.js';
|
|
7
|
+
import { HeaderBarContextProvider } from './header-bar-context.js';
|
|
7
8
|
import { joinPath } from './join-path.js';
|
|
8
9
|
import i18n from './locales/index.js';
|
|
9
10
|
import { Logo } from './logo.js';
|
|
@@ -36,9 +37,12 @@ export const HeaderBar = _ref => {
|
|
|
36
37
|
|
|
37
38
|
let {
|
|
38
39
|
appName,
|
|
39
|
-
className
|
|
40
|
+
className,
|
|
41
|
+
updateAvailable,
|
|
42
|
+
onApplyAvailableUpdate
|
|
40
43
|
} = _ref;
|
|
41
44
|
const {
|
|
45
|
+
appName: configAppName,
|
|
42
46
|
baseUrl,
|
|
43
47
|
pwaEnabled,
|
|
44
48
|
headerbar: {
|
|
@@ -57,24 +61,28 @@ export const HeaderBar = _ref => {
|
|
|
57
61
|
icon: getPath(app.icon),
|
|
58
62
|
defaultAction: getPath(app.defaultAction)
|
|
59
63
|
}));
|
|
60
|
-
}, [data]); // See https://jira.dhis2.org/browse/LIBS-180
|
|
64
|
+
}, [data, baseUrl]); // See https://jira.dhis2.org/browse/LIBS-180
|
|
61
65
|
|
|
62
66
|
if (!loading && !error) {
|
|
63
|
-
// TODO: This will run every render which is probably wrong!
|
|
67
|
+
// TODO: This will run every render which is probably wrong!
|
|
68
|
+
// Also, setting the global locale shouldn't be done in the headerbar
|
|
64
69
|
const locale = data.user.settings.keyUiLocale || 'en';
|
|
65
70
|
i18n.setDefaultNamespace('default');
|
|
66
71
|
i18n.changeLanguage(locale);
|
|
67
72
|
}
|
|
68
73
|
|
|
69
|
-
return /*#__PURE__*/React.createElement(
|
|
70
|
-
|
|
74
|
+
return /*#__PURE__*/React.createElement(HeaderBarContextProvider, {
|
|
75
|
+
updateAvailable: updateAvailable,
|
|
76
|
+
onApplyAvailableUpdate: onApplyAvailableUpdate
|
|
77
|
+
}, /*#__PURE__*/React.createElement("header", {
|
|
78
|
+
className: _JSXStyle.dynamic([["3860078320", [colors.white]]]) + " " + (className || "")
|
|
71
79
|
}, /*#__PURE__*/React.createElement("div", {
|
|
72
|
-
className: _JSXStyle.dynamic([["
|
|
80
|
+
className: _JSXStyle.dynamic([["3860078320", [colors.white]]]) + " " + "main"
|
|
73
81
|
}, !loading && !error && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Logo, null), /*#__PURE__*/React.createElement(Title, {
|
|
74
|
-
app: appName,
|
|
82
|
+
app: appName || configAppName,
|
|
75
83
|
instance: data.title.applicationTitle
|
|
76
84
|
}), /*#__PURE__*/React.createElement("div", {
|
|
77
|
-
className: _JSXStyle.dynamic([["
|
|
85
|
+
className: _JSXStyle.dynamic([["3860078320", [colors.white]]]) + " " + "right-control-spacer"
|
|
78
86
|
}), (pwaEnabled || showOnlineStatus) && /*#__PURE__*/React.createElement(OnlineStatus, null), /*#__PURE__*/React.createElement(Notifications, {
|
|
79
87
|
interpretations: data.notifications.unreadInterpretations,
|
|
80
88
|
messages: data.notifications.unreadMessageConversations,
|
|
@@ -89,11 +97,13 @@ export const HeaderBar = _ref => {
|
|
|
89
97
|
}))), (pwaEnabled || showOnlineStatus) && !loading && !error && /*#__PURE__*/React.createElement(OnlineStatus, {
|
|
90
98
|
dense: true
|
|
91
99
|
}), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
92
|
-
id: "
|
|
100
|
+
id: "3860078320",
|
|
93
101
|
dynamic: [colors.white]
|
|
94
|
-
}, [".main.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;background-color:#2c6693;border-bottom:1px solid rgba(32,32,32,0.15);color:".concat(colors.white, ";height:48px;}"), ".right-control-spacer.__jsx-style-dynamic-selector{margin-left:auto;}"]));
|
|
102
|
+
}, [".main.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;background-color:#2c6693;border-bottom:1px solid rgba(32,32,32,0.15);color:".concat(colors.white, ";height:48px;}"), ".right-control-spacer.__jsx-style-dynamic-selector{margin-left:auto;}"])));
|
|
95
103
|
};
|
|
96
104
|
HeaderBar.propTypes = {
|
|
97
105
|
appName: PropTypes.string,
|
|
98
|
-
className: PropTypes.string
|
|
106
|
+
className: PropTypes.string,
|
|
107
|
+
updateAvailable: PropTypes.bool,
|
|
108
|
+
onApplyAvailableUpdate: PropTypes.func
|
|
99
109
|
};
|
|
@@ -1,27 +1,9 @@
|
|
|
1
|
-
import { CustomDataProvider
|
|
1
|
+
import { CustomDataProvider } from '@dhis2/app-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { createDecoratorProvider, providerConfig } from './__e2e__/stories/common.js';
|
|
3
4
|
import { HeaderBar } from './header-bar.js';
|
|
4
5
|
const subtitle = 'The common navigation bar used in all DHIS2 apps';
|
|
5
6
|
const description = "\nThe header bar is mandatory for all apps. This creates a stable, understandable point of reference for the user across all kinds of different apps. It must always be displayed fixed to the top of the screen. Do not interfere or obstruct interaction with the header bar.\n\nThe header bar is included automatically with the App Shell and should not need any configuration.\n\n#### Theme\n\nThe header bar can be themeed to suit the brand/color of your DHIS2 instance. The color of the text/icons will be automatically adjusted based on the selected color.\n\n```js\nimport { HeaderBar } from '@dhis2/ui'\n```\n";
|
|
6
|
-
export default {
|
|
7
|
-
title: 'Header Bar',
|
|
8
|
-
component: HeaderBar,
|
|
9
|
-
parameters: {
|
|
10
|
-
componentSubtitle: subtitle,
|
|
11
|
-
docs: {
|
|
12
|
-
description: {
|
|
13
|
-
component: description
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
},
|
|
17
|
-
args: {
|
|
18
|
-
appName: 'Example!'
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
const mockConfig = {
|
|
22
|
-
baseUrl: 'https://debug.dhis2.org/dev/',
|
|
23
|
-
apiVersion: 33
|
|
24
|
-
};
|
|
25
7
|
const customData = {
|
|
26
8
|
'systemSettings/applicationTitle': {
|
|
27
9
|
applicationTitle: 'Foobar'
|
|
@@ -142,56 +124,64 @@ const customAuthoritiesData = { ...customData,
|
|
|
142
124
|
authorities: ['M_dhis-web-messaging']
|
|
143
125
|
}
|
|
144
126
|
};
|
|
145
|
-
export
|
|
146
|
-
|
|
147
|
-
|
|
127
|
+
export default {
|
|
128
|
+
title: 'Header Bar',
|
|
129
|
+
component: HeaderBar,
|
|
130
|
+
parameters: {
|
|
131
|
+
componentSubtitle: subtitle,
|
|
132
|
+
docs: {
|
|
133
|
+
description: {
|
|
134
|
+
component: description
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
decorators: [createDecoratorProvider()]
|
|
139
|
+
};
|
|
140
|
+
export const Default = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
148
141
|
data: customData
|
|
149
|
-
}, /*#__PURE__*/React.createElement(HeaderBar,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
143
|
+
appName: "Example!"
|
|
144
|
+
}));
|
|
145
|
+
export const CustomLogoWideDimension = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
153
146
|
data: customLogoData
|
|
154
|
-
}, /*#__PURE__*/React.createElement(HeaderBar,
|
|
147
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
148
|
+
appName: "Example!"
|
|
149
|
+
}));
|
|
155
150
|
CustomLogoWideDimension.storyName = 'Custom Logo (wide dimension)';
|
|
156
|
-
export const NonEnglishUserLocale =
|
|
157
|
-
config: mockConfig
|
|
158
|
-
}, /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
151
|
+
export const NonEnglishUserLocale = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
159
152
|
data: customLocaleData
|
|
160
|
-
}, /*#__PURE__*/React.createElement(HeaderBar,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
};
|
|
153
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
154
|
+
appName: "Exemple!"
|
|
155
|
+
}));
|
|
164
156
|
NonEnglishUserLocale.storyName = 'Non-english user locale';
|
|
165
|
-
export const NoAuthorityForInterpretationsApp =
|
|
166
|
-
config: mockConfig
|
|
167
|
-
}, /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
157
|
+
export const NoAuthorityForInterpretationsApp = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
168
158
|
data: customAuthoritiesData
|
|
169
|
-
}, /*#__PURE__*/React.createElement(HeaderBar,
|
|
159
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
160
|
+
appName: "Example!"
|
|
161
|
+
}));
|
|
170
162
|
NoAuthorityForInterpretationsApp.storyName = 'No authority for interpretations app';
|
|
171
|
-
export const Loading =
|
|
172
|
-
config: mockConfig
|
|
173
|
-
}, /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
163
|
+
export const Loading = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
174
164
|
options: {
|
|
175
165
|
loadForever: true
|
|
176
166
|
}
|
|
177
|
-
}, /*#__PURE__*/React.createElement(HeaderBar,
|
|
167
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
168
|
+
appName: "Example!"
|
|
169
|
+
}));
|
|
178
170
|
Loading.storyName = 'Loading...';
|
|
179
|
-
export const Error =
|
|
180
|
-
config: mockConfig
|
|
181
|
-
}, /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
171
|
+
export const Error = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
182
172
|
data: {}
|
|
183
|
-
}, /*#__PURE__*/React.createElement(HeaderBar,
|
|
173
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
174
|
+
appName: "Exemple!"
|
|
175
|
+
}));
|
|
184
176
|
Error.storyName = 'Error!';
|
|
185
|
-
export const WithOnlineStatus =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}, /*#__PURE__*/React.createElement(HeaderBar, args)));
|
|
194
|
-
};
|
|
177
|
+
export const WithOnlineStatus = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
178
|
+
data: customData
|
|
179
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
180
|
+
appName: "Exemple!"
|
|
181
|
+
}));
|
|
182
|
+
WithOnlineStatus.decorators = [createDecoratorProvider({ ...providerConfig,
|
|
183
|
+
pwaEnabled: true
|
|
184
|
+
})];
|
|
195
185
|
WithOnlineStatus.parameters = {
|
|
196
186
|
docs: {
|
|
197
187
|
description: {
|
|
@@ -202,19 +192,17 @@ WithOnlineStatus.parameters = {
|
|
|
202
192
|
}
|
|
203
193
|
}
|
|
204
194
|
};
|
|
205
|
-
export const WithLastOnlineInfo =
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
}, /*#__PURE__*/React.createElement(HeaderBar, args)));
|
|
217
|
-
};
|
|
195
|
+
export const WithLastOnlineInfo = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
196
|
+
data: customData
|
|
197
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
198
|
+
appName: "Exemple!"
|
|
199
|
+
}));
|
|
200
|
+
WithLastOnlineInfo.decorators = [createDecoratorProvider({ ...providerConfig,
|
|
201
|
+
pwaEnabled: true,
|
|
202
|
+
headerbar: {
|
|
203
|
+
onlineStatusInfo: 'LAST_ONLINE'
|
|
204
|
+
}
|
|
205
|
+
})];
|
|
218
206
|
WithLastOnlineInfo.parameters = {
|
|
219
207
|
docs: {
|
|
220
208
|
description: {
|
|
@@ -222,4 +210,10 @@ WithLastOnlineInfo.parameters = {
|
|
|
222
210
|
time since last online.'
|
|
223
211
|
}
|
|
224
212
|
}
|
|
225
|
-
};
|
|
213
|
+
};
|
|
214
|
+
export const WithUpdateNotification = () => /*#__PURE__*/React.createElement(CustomDataProvider, {
|
|
215
|
+
data: customData
|
|
216
|
+
}, /*#__PURE__*/React.createElement(HeaderBar, {
|
|
217
|
+
appName: "Data Visualizer",
|
|
218
|
+
updateAvailable: true
|
|
219
|
+
}));
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"Search apps": "Search apps",
|
|
3
|
+
"DHIS2 {{dhis2Version}}": "DHIS2 {{dhis2Version}}",
|
|
4
|
+
"DHIS2 version unknown": "DHIS2 version unknown",
|
|
5
|
+
"{{appName}} version unknown": "{{appName}} version unknown",
|
|
6
|
+
"App {{appVersion}}": "App {{appVersion}}",
|
|
7
|
+
"App version unknown": "App version unknown",
|
|
8
|
+
"Debug info": "Debug info",
|
|
9
|
+
"Close": "Close",
|
|
10
|
+
"Copy debug info": "Copy debug info",
|
|
3
11
|
"Last online {{relativeTime}}": "Last online {{relativeTime}}",
|
|
4
12
|
"Online": "Online",
|
|
5
13
|
"Offline": "Offline",
|
|
@@ -8,5 +16,8 @@
|
|
|
8
16
|
"Account": "Account",
|
|
9
17
|
"Help": "Help",
|
|
10
18
|
"About DHIS2": "About DHIS2",
|
|
11
|
-
"Logout": "Logout"
|
|
19
|
+
"Logout": "Logout",
|
|
20
|
+
"New {{appName}} version available": "New {{appName}} version available",
|
|
21
|
+
"New app version available": "New app version available",
|
|
22
|
+
"Click to reload": "Click to reload"
|
|
12
23
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
2
|
+
export const useOnDocClick = (containerRef, hide) => {
|
|
3
|
+
const onDocClick = useMemo(() => {
|
|
4
|
+
return evt => {
|
|
5
|
+
if (!containerRef.current) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (!containerRef.current.contains(evt.target)) {
|
|
10
|
+
hide();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}, [containerRef, hide]);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
document.addEventListener('click', onDocClick);
|
|
16
|
+
return () => {
|
|
17
|
+
document.removeEventListener('click', onDocClick);
|
|
18
|
+
};
|
|
19
|
+
}, [onDocClick]);
|
|
20
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-hooks';
|
|
2
|
+
import { useOnDocClick } from './use-on-doc-click.js';
|
|
3
|
+
describe('useOnDocClick', () => {
|
|
4
|
+
let eventListenerMap = {};
|
|
5
|
+
const hide = jest.fn();
|
|
6
|
+
jest.spyOn(document, 'addEventListener').mockImplementation((event, callback) => {
|
|
7
|
+
eventListenerMap[event] = callback;
|
|
8
|
+
});
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
eventListenerMap = {};
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
document.addEventListener.mockClear();
|
|
14
|
+
hide.mockClear();
|
|
15
|
+
});
|
|
16
|
+
it('should call the hide function when clicking outside', () => {
|
|
17
|
+
const el = document.createElement('span');
|
|
18
|
+
const containerRef = {
|
|
19
|
+
current: el
|
|
20
|
+
};
|
|
21
|
+
renderHook(() => useOnDocClick(containerRef, hide));
|
|
22
|
+
eventListenerMap.click({
|
|
23
|
+
target: document.body
|
|
24
|
+
});
|
|
25
|
+
expect(hide).toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
it('should not call the hide function when clicking inside', () => {
|
|
28
|
+
const el = document.createElement('span');
|
|
29
|
+
const containerRef = {
|
|
30
|
+
current: el
|
|
31
|
+
};
|
|
32
|
+
renderHook(() => useOnDocClick(containerRef, hide));
|
|
33
|
+
eventListenerMap.click({
|
|
34
|
+
target: el
|
|
35
|
+
});
|
|
36
|
+
expect(hide).not.toHaveBeenCalled();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './profile-menu.js';
|
|
File without changes
|
|
@@ -4,15 +4,17 @@ import { Center } from '@dhis2-ui/center';
|
|
|
4
4
|
import { Divider } from '@dhis2-ui/divider';
|
|
5
5
|
import { Layer } from '@dhis2-ui/layer';
|
|
6
6
|
import { CircularLoader } from '@dhis2-ui/loader';
|
|
7
|
-
import { MenuItem } from '@dhis2-ui/menu';
|
|
8
|
-
import {
|
|
7
|
+
import { MenuDivider, MenuItem } from '@dhis2-ui/menu';
|
|
8
|
+
import { clearSensitiveCaches, useConfig } from '@dhis2/app-runtime';
|
|
9
9
|
import { colors } from '@dhis2/ui-constants';
|
|
10
10
|
import { IconSettings24, IconInfo24, IconLogOut24, IconUser24, IconQuestion24 } from '@dhis2/ui-icons';
|
|
11
11
|
import PropTypes from 'prop-types';
|
|
12
12
|
import React, { useState } from 'react';
|
|
13
|
+
import { DebugInfoMenuItem } from '../debug-info/debug-info-menu-item.js';
|
|
13
14
|
import { joinPath } from '../join-path.js';
|
|
14
15
|
import i18n from '../locales/index.js';
|
|
15
16
|
import { ProfileHeader } from './profile-header.js';
|
|
17
|
+
import { UpdateNotification } from './update-notification.js';
|
|
16
18
|
|
|
17
19
|
const LoadingMask = () => /*#__PURE__*/React.createElement(Layer, {
|
|
18
20
|
translucent: true,
|
|
@@ -25,7 +27,9 @@ const ProfileContents = _ref => {
|
|
|
25
27
|
name,
|
|
26
28
|
email,
|
|
27
29
|
avatarId,
|
|
28
|
-
helpUrl
|
|
30
|
+
helpUrl,
|
|
31
|
+
hideProfileMenu,
|
|
32
|
+
showDebugInfoModal
|
|
29
33
|
} = _ref;
|
|
30
34
|
const {
|
|
31
35
|
baseUrl
|
|
@@ -86,37 +90,39 @@ const ProfileContents = _ref => {
|
|
|
86
90
|
icon: /*#__PURE__*/React.createElement(IconLogOut24, {
|
|
87
91
|
color: colors.grey700
|
|
88
92
|
})
|
|
93
|
+
}), /*#__PURE__*/React.createElement(MenuDivider, {
|
|
94
|
+
dense: true
|
|
95
|
+
}), /*#__PURE__*/React.createElement(DebugInfoMenuItem, {
|
|
96
|
+
hideProfileMenu: hideProfileMenu,
|
|
97
|
+
showDebugInfoModal: showDebugInfoModal
|
|
98
|
+
}), /*#__PURE__*/React.createElement(UpdateNotification, {
|
|
99
|
+
hideProfileMenu: hideProfileMenu
|
|
89
100
|
}))), loading && /*#__PURE__*/React.createElement(LoadingMask, null), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
90
101
|
id: "2099675810"
|
|
91
102
|
}, ["div.jsx-2099675810{width:100%;padding:0;}", "ul.jsx-2099675810{padding:0;margin:0;}", "a.jsx-2099675810,a.jsx-2099675810:hover,a.jsx-2099675810:focus,a.jsx-2099675810:active,a.jsx-2099675810:visited{-webkit-text-decoration:none;text-decoration:none;display:block;}"]));
|
|
92
103
|
};
|
|
93
104
|
|
|
94
105
|
ProfileContents.propTypes = {
|
|
106
|
+
hideProfileMenu: PropTypes.func.isRequired,
|
|
107
|
+
showDebugInfoModal: PropTypes.func.isRequired,
|
|
95
108
|
avatarId: PropTypes.string,
|
|
96
109
|
email: PropTypes.string,
|
|
97
110
|
helpUrl: PropTypes.string,
|
|
98
111
|
name: PropTypes.string
|
|
99
112
|
};
|
|
100
113
|
export const ProfileMenu = _ref2 => {
|
|
101
|
-
let {
|
|
102
|
-
avatarId,
|
|
103
|
-
name,
|
|
104
|
-
email,
|
|
105
|
-
helpUrl
|
|
114
|
+
let { ...props
|
|
106
115
|
} = _ref2;
|
|
107
116
|
return /*#__PURE__*/React.createElement("div", {
|
|
108
117
|
"data-test": "headerbar-profile-menu",
|
|
109
|
-
className: "jsx-
|
|
110
|
-
}, /*#__PURE__*/React.createElement(ProfileContents, {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
avatarId: avatarId,
|
|
114
|
-
helpUrl: helpUrl
|
|
115
|
-
}), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
116
|
-
id: "3620236321"
|
|
117
|
-
}, ["div.jsx-3620236321{z-index:10000;position:absolute;top:34px;right:-6px;width:310px;border-top:4px solid transparent;}"]));
|
|
118
|
+
className: "jsx-1689179824"
|
|
119
|
+
}, /*#__PURE__*/React.createElement(ProfileContents, props), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
120
|
+
id: "1689179824"
|
|
121
|
+
}, ["div.jsx-1689179824{z-index:10000;position:absolute;top:34px;right:-6px;width:320px;border-top:4px solid transparent;}"]));
|
|
118
122
|
};
|
|
119
123
|
ProfileMenu.propTypes = {
|
|
124
|
+
hideProfileMenu: PropTypes.func.isRequired,
|
|
125
|
+
showDebugInfoModal: PropTypes.func.isRequired,
|
|
120
126
|
avatarId: PropTypes.string,
|
|
121
127
|
email: PropTypes.string,
|
|
122
128
|
helpUrl: PropTypes.string,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import _JSXStyle from "styled-jsx/style";
|
|
2
|
+
import { MenuItem } from '@dhis2-ui/menu';
|
|
3
|
+
import { useConfig } from '@dhis2/app-runtime';
|
|
4
|
+
import { colors } from '@dhis2/ui-constants';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { useHeaderBarContext } from '../header-bar-context.js';
|
|
8
|
+
import i18n from '../locales/index.js';
|
|
9
|
+
export function UpdateNotification(_ref) {
|
|
10
|
+
let {
|
|
11
|
+
hideProfileMenu
|
|
12
|
+
} = _ref;
|
|
13
|
+
const {
|
|
14
|
+
appName
|
|
15
|
+
} = useConfig();
|
|
16
|
+
const {
|
|
17
|
+
updateAvailable,
|
|
18
|
+
onApplyAvailableUpdate
|
|
19
|
+
} = useHeaderBarContext();
|
|
20
|
+
|
|
21
|
+
const onClick = () => {
|
|
22
|
+
hideProfileMenu();
|
|
23
|
+
onApplyAvailableUpdate === null || onApplyAvailableUpdate === void 0 ? void 0 : onApplyAvailableUpdate();
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const updateNotificationLabel = /*#__PURE__*/React.createElement("div", {
|
|
27
|
+
className: _JSXStyle.dynamic([["4135170305", [colors.blue600]]]) + " " + "root"
|
|
28
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
29
|
+
className: _JSXStyle.dynamic([["4135170305", [colors.blue600]]]) + " " + "badge"
|
|
30
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
31
|
+
className: _JSXStyle.dynamic([["4135170305", [colors.blue600]]]) + " " + "spacer"
|
|
32
|
+
}), /*#__PURE__*/React.createElement("div", {
|
|
33
|
+
className: _JSXStyle.dynamic([["4135170305", [colors.blue600]]]) + " " + "message"
|
|
34
|
+
}, appName ? i18n.t('New {{appName}} version available', {
|
|
35
|
+
appName
|
|
36
|
+
}) : i18n.t('New app version available'), /*#__PURE__*/React.createElement("br", {
|
|
37
|
+
className: _JSXStyle.dynamic([["4135170305", [colors.blue600]]])
|
|
38
|
+
}), i18n.t('Click to reload')), /*#__PURE__*/React.createElement(_JSXStyle, {
|
|
39
|
+
id: "4135170305",
|
|
40
|
+
dynamic: [colors.blue600]
|
|
41
|
+
}, [".root.__jsx-style-dynamic-selector{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:14px;line-height:17px;}", ".badge.__jsx-style-dynamic-selector{display:inline-block;width:12px;height:12px;margin:0 8px;border-radius:6px;background-color:".concat(colors.blue600, ";}"), ".spacer.__jsx-style-dynamic-selector{display:inline-block;width:8px;}", ".message.__jsx-style-dynamic-selector{display:inline-block;}"]));
|
|
42
|
+
return updateAvailable ? /*#__PURE__*/React.createElement(MenuItem, {
|
|
43
|
+
dense: true,
|
|
44
|
+
onClick: onClick,
|
|
45
|
+
label: updateNotificationLabel,
|
|
46
|
+
dataTest: "dhis2-ui-headerbar-updatenotification"
|
|
47
|
+
}) : null;
|
|
48
|
+
}
|
|
49
|
+
UpdateNotification.propTypes = {
|
|
50
|
+
hideProfileMenu: PropTypes.func.isRequired
|
|
51
|
+
};
|