@utahdts/utah-design-system-header 1.2.1 → 1.3.0
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/dist/index.d.ts +576 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/style.css +1 -1
- package/dist/utah-design-system-header.es.js +5 -5
- package/dist/utah-design-system-header.umd.js +4 -4
- package/package.json +5 -4
- package/src/index.js +11 -0
- package/src/js/enumerations/childrenMenuTypes.js +13 -0
- package/src/js/enumerations/domConstants.js +149 -0
- package/src/js/enumerations/environments.js +16 -0
- package/src/js/enumerations/events.js +14 -0
- package/src/js/enumerations/popupPlacement.js +32 -0
- package/src/js/enumerations/sizes.js +12 -0
- package/src/js/enumerations/utahIdUrls.js +10 -0
- package/src/js/lifecycle/globalEvents.js +81 -0
- package/src/js/lifecycle/hookupMobileActionItemKeyboarding.js +114 -0
- package/src/js/lifecycle/lifecycle.js +180 -0
- package/src/js/misc/checkForError.js +16 -0
- package/src/js/misc/findRecursive.js +39 -0
- package/src/js/misc/isString.js +8 -0
- package/src/js/misc/isTouchDevice.js +9 -0
- package/src/js/misc/jsDocTypes.js +235 -0
- package/src/js/misc/notNull.js +15 -0
- package/src/js/misc/popupFocusHandler.js +209 -0
- package/src/js/misc/renderDOMSingle.js +61 -0
- package/src/js/misc/showHideElement.js +15 -0
- package/src/js/misc/toHash.js +18 -0
- package/src/js/misc/uuidv4.js +8 -0
- package/src/js/misc/valueOrFunctionValue.js +13 -0
- package/src/js/renderables/_html/NewTabAccessibility.html +4 -0
- package/src/js/renderables/actionItems/ActionItems.js +29 -0
- package/src/js/renderables/actionItems/html/ActionItem.html +5 -0
- package/src/js/renderables/actionItems/html/ActionItemMenuContent.html +3 -0
- package/src/js/renderables/actionItems/html/ActionItemsWrapper.html +1 -0
- package/src/js/renderables/actionItems/html/BadgeWrapperHtml.html +4 -0
- package/src/js/renderables/actionItems/html/MobileActionItem.html +7 -0
- package/src/js/renderables/actionItems/renderActionItem.js +103 -0
- package/src/js/renderables/actionItems/renderActionItemBadge.js +50 -0
- package/src/js/renderables/actionItems/renderMobileActionItem.js +97 -0
- package/src/js/renderables/actionItems/renderMobileActionItems.js +83 -0
- package/src/js/renderables/citizenExperience/CitizenExperience.js +24 -0
- package/src/js/renderables/citizenExperience/html/CitizenExperienceWrapper.html +1 -0
- package/src/js/renderables/citizenExperience/html/CitizenExperienceWrapperMobile.html +1 -0
- package/src/js/renderables/footer/html/Footer.html +41 -0
- package/src/js/renderables/footer/renderFooter.js +111 -0
- package/src/js/renderables/headerWrapper/HeaderWrapper.js +46 -0
- package/src/js/renderables/headerWrapper/html/HeaderLogoWrapper.html +1 -0
- package/src/js/renderables/headerWrapper/html/HeaderWrapper.html +1 -0
- package/src/js/renderables/headerWrapper/html/VerticalLine.html +1 -0
- package/src/js/renderables/icons/html/AlertIcon.html +1 -0
- package/src/js/renderables/icons/html/ChevronIcon.html +1 -0
- package/src/js/renderables/icons/html/ExternalLinkIcon.html +7 -0
- package/src/js/renderables/icons/html/GearIcon.html +1 -0
- package/src/js/renderables/icons/html/QuestionIcon.html +1 -0
- package/src/js/renderables/icons/html/WaffleIcon.html +1 -0
- package/src/js/renderables/logoTitle/LogoTitle.js +63 -0
- package/src/js/renderables/logoTitle/html/LogoTitleWrapper.html +4 -0
- package/src/js/renderables/logoTitle/html/LogoTitleWrapperLink.html +4 -0
- package/src/js/renderables/mainMenu/html/MainMenuItem.html +13 -0
- package/src/js/renderables/mainMenu/html/MainMenuWrapper.html +26 -0
- package/src/js/renderables/mainMenu/renderMainMenu.js +258 -0
- package/src/js/renderables/menu/html/MenuWithTitle.html +3 -0
- package/src/js/renderables/menu/renderMenuWithTitle.js +24 -0
- package/src/js/renderables/mobile/addMobileMenuContentItem.js +37 -0
- package/src/js/renderables/mobile/hookupHamburger.js +112 -0
- package/src/js/renderables/mobile/hookupUtahIdInMobileMenu.js +79 -0
- package/src/js/renderables/mobile/html/MobileMenuContentItemWrapper.html +2 -0
- package/src/js/renderables/mobile/html/MobileMenuWrapper.html +32 -0
- package/src/js/renderables/mobile/mobileMenuInteractionHandler.js +155 -0
- package/src/js/renderables/mobile/renderMobileMenuHomeMenu.js +15 -0
- package/src/js/renderables/mobile/util/getHamburgerElements.js +22 -0
- package/src/js/renderables/mobile/util/showHideHamburgerElements.js +32 -0
- package/src/js/renderables/popup/html/Popup.html +5 -0
- package/src/js/renderables/popup/renderPopup.js +41 -0
- package/src/js/renderables/popupMenu/html/PopupMenu.html +1 -0
- package/src/js/renderables/popupMenu/html/PopupMenuItem.html +12 -0
- package/src/js/renderables/popupMenu/renderPopupMenu.js +341 -0
- package/src/js/renderables/search/html/SearchModal.html +25 -0
- package/src/js/renderables/search/showSearchModal.js +132 -0
- package/src/js/renderables/tooltip/hookupTooltip.js +82 -0
- package/src/js/renderables/tooltip/html/Tooltip.html +5 -0
- package/src/js/renderables/utahId/UtahId.js +227 -0
- package/src/js/renderables/utahId/html/UtahIdButton.html +1 -0
- package/src/js/renderables/utahId/html/UtahIdWrapper.html +1 -0
- package/src/js/renderables/utahLogo/UtahLogo.js +48 -0
- package/src/js/renderables/utahLogo/html/UtahLogoLarge.html +17 -0
- package/src/js/renderables/utahLogo/html/UtahLogoMedium.html +17 -0
- package/src/js/renderables/utahLogo/html/UtahOfficialWebsiteHoverContent.html +4 -0
- package/src/js/renderables/utahLogo/html/UtahOfficialWebsitePopupContent.html +41 -0
- package/src/js/renderables/utahLogo/renderOfficialWebsite.js +139 -0
- package/src/js/settings/defaultSettings.js +31 -0
- package/src/js/settings/getUtahHeaderSettings.js +16 -0
- package/src/js/settings/settings.js +69 -0
- package/src/js/settings/settingsKeeper.js +47 -0
- package/src/js/utahId/httpRequest.js +42 -0
- package/src/js/utahId/utahIdData.js +165 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
// eslint-disable-next-line import/no-unresolved
|
|
3
|
+
import UtahOfficialWebsitePopupContentHtml from './html/UtahOfficialWebsitePopupContent.html?raw';
|
|
4
|
+
|
|
5
|
+
import domConstants, { getCssClassSelector } from '../../enumerations/domConstants';
|
|
6
|
+
import notNull from '../../misc/notNull';
|
|
7
|
+
import renderDOMSingle from '../../misc/renderDOMSingle';
|
|
8
|
+
import uuidv4 from '../../misc/uuidv4';
|
|
9
|
+
import { hideMobileMenu } from '../mobile/util/showHideHamburgerElements';
|
|
10
|
+
|
|
11
|
+
export function closeOfficialWebsite() {
|
|
12
|
+
const officialWebsiteWrapper = notNull(
|
|
13
|
+
document.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_WRAPPER)),
|
|
14
|
+
'openOfficialWebsite: official wrapper not found'
|
|
15
|
+
);
|
|
16
|
+
const logoWrapper = notNull(
|
|
17
|
+
document.querySelector(getCssClassSelector(domConstants.LOGO)),
|
|
18
|
+
'openOfficialWebsite: logoWrapper not found'
|
|
19
|
+
);
|
|
20
|
+
const logoButton = notNull(
|
|
21
|
+
logoWrapper.querySelector(getCssClassSelector(domConstants.LOGO_SVG)),
|
|
22
|
+
'openOfficialWebsite: logoButton not found'
|
|
23
|
+
);
|
|
24
|
+
const closeButton = notNull(
|
|
25
|
+
officialWebsiteWrapper.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_CLOSE_BUTTON)),
|
|
26
|
+
'openOfficialWebsite: official close button not found'
|
|
27
|
+
);
|
|
28
|
+
officialWebsiteWrapper.classList.add(domConstants.VISUALLY_HIDDEN);
|
|
29
|
+
|
|
30
|
+
logoButton.setAttribute('aria-expanded', 'false');
|
|
31
|
+
officialWebsiteWrapper.setAttribute('aria-hidden', 'true');
|
|
32
|
+
// shift tabbing from mobile menu item content's first action item button would tab in to this
|
|
33
|
+
// hidden "official website" content. setting the tabindex to -1 makes this not happen
|
|
34
|
+
closeButton.setAttribute('tabIndex', '-1');
|
|
35
|
+
officialWebsiteWrapper.setAttribute('tabIndex', '-1');
|
|
36
|
+
|
|
37
|
+
// hide all tooltips when button is clicked because the popup opens
|
|
38
|
+
const toolTips = document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER));
|
|
39
|
+
toolTips?.forEach((tooltip) => {
|
|
40
|
+
tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN);
|
|
41
|
+
tooltip.classList.remove(domConstants.TOOLTIP__WRAPPER__VISIBLE);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function openOfficialWebsite() {
|
|
46
|
+
const officialWebsiteWrapper = notNull(
|
|
47
|
+
document.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_WRAPPER)),
|
|
48
|
+
'openOfficialWebsite: official wrapper not found'
|
|
49
|
+
);
|
|
50
|
+
const logoWrapper = notNull(
|
|
51
|
+
document.querySelector(getCssClassSelector(domConstants.LOGO)),
|
|
52
|
+
'openOfficialWebsite: logoWrapper not found'
|
|
53
|
+
);
|
|
54
|
+
const logoButton = notNull(
|
|
55
|
+
logoWrapper.querySelector(getCssClassSelector(domConstants.LOGO_SVG)),
|
|
56
|
+
'openOfficialWebsite: logoButton not found'
|
|
57
|
+
);
|
|
58
|
+
const closeButton = notNull(
|
|
59
|
+
officialWebsiteWrapper.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_CLOSE_BUTTON)),
|
|
60
|
+
'openOfficialWebsite: official close button not found'
|
|
61
|
+
);
|
|
62
|
+
officialWebsiteWrapper.classList.remove(domConstants.VISUALLY_HIDDEN);
|
|
63
|
+
|
|
64
|
+
closeButton.removeAttribute('tabIndex');
|
|
65
|
+
officialWebsiteWrapper.removeAttribute('tabIndex');
|
|
66
|
+
logoButton.setAttribute('aria-expanded', 'true');
|
|
67
|
+
officialWebsiteWrapper.setAttribute('aria-hidden', 'false');
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
officialWebsiteWrapper.focus();
|
|
70
|
+
|
|
71
|
+
// hide all tooltips when button is clicked because the popup opens
|
|
72
|
+
const toolTips = document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER));
|
|
73
|
+
toolTips?.forEach((tooltip) => {
|
|
74
|
+
tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN);
|
|
75
|
+
tooltip.classList.remove(domConstants.TOOLTIP__WRAPPER__VISIBLE);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
hideMobileMenu();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @returns {Element}
|
|
83
|
+
*/
|
|
84
|
+
export default function renderOfficialWebsite() {
|
|
85
|
+
const officialWebsiteWrapper = renderDOMSingle(UtahOfficialWebsitePopupContentHtml);
|
|
86
|
+
|
|
87
|
+
const logoWrapper = document.querySelector(getCssClassSelector(domConstants.LOGO));
|
|
88
|
+
const logoButton = /** @type {HTMLElement} */(logoWrapper?.querySelector(getCssClassSelector(domConstants.LOGO_SVG)));
|
|
89
|
+
if (!logoButton) {
|
|
90
|
+
throw new Error('renderOfficialWebsite: logoButton not found');
|
|
91
|
+
}
|
|
92
|
+
if (logoButton.onclick) {
|
|
93
|
+
throw new Error('renderOfficialWebsite: logoButton already has an onclick');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const closeButton = officialWebsiteWrapper.querySelector(getCssClassSelector(domConstants.LOGO_OFFICIAL_CLOSE_BUTTON));
|
|
97
|
+
if (!closeButton) {
|
|
98
|
+
throw new Error('renderOfficialWebsite: closeButton not found');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
logoButton.onclick = () => {
|
|
102
|
+
officialWebsiteWrapper.classList.toggle(domConstants.VISUALLY_HIDDEN);
|
|
103
|
+
if (officialWebsiteWrapper.classList.contains(domConstants.VISUALLY_HIDDEN)) {
|
|
104
|
+
closeOfficialWebsite();
|
|
105
|
+
} else {
|
|
106
|
+
openOfficialWebsite();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// hide all tooltips when button is clicked because the popup opens
|
|
110
|
+
const toolTips = document.querySelectorAll(getCssClassSelector(domConstants.TOOLTIP__WRAPPER));
|
|
111
|
+
toolTips?.forEach((tooltip) => {
|
|
112
|
+
tooltip.classList.add(domConstants.TOOLTIP__WRAPPER__HIDDEN);
|
|
113
|
+
tooltip.classList.remove(domConstants.TOOLTIP__WRAPPER__VISIBLE);
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// @ts-ignore
|
|
118
|
+
closeButton.onclick = () => {
|
|
119
|
+
officialWebsiteWrapper.classList.toggle(domConstants.VISUALLY_HIDDEN);
|
|
120
|
+
logoButton.focus();
|
|
121
|
+
logoButton.setAttribute('aria-expanded', 'false');
|
|
122
|
+
officialWebsiteWrapper.setAttribute('aria-hidden', 'true');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (!closeButton.id) {
|
|
126
|
+
closeButton.id = uuidv4();
|
|
127
|
+
}
|
|
128
|
+
if (!officialWebsiteWrapper.id) {
|
|
129
|
+
officialWebsiteWrapper.id = uuidv4();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// hook up aria
|
|
133
|
+
logoButton.setAttribute('aria-controls', officialWebsiteWrapper.id);
|
|
134
|
+
logoButton.setAttribute('aria-expanded', 'false');
|
|
135
|
+
officialWebsiteWrapper.setAttribute('aria-hidden', 'true');
|
|
136
|
+
officialWebsiteWrapper.setAttribute('aria-labelledby', logoButton.id);
|
|
137
|
+
|
|
138
|
+
return officialWebsiteWrapper;
|
|
139
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import sizes from '../enumerations/sizes';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../misc/jsDocTypes').Settings} Settings
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* !~! Do not use defaultSettings directly !~!
|
|
10
|
+
* Interact with `settings` using getUtahHeaderSettings() and setUtahHeaderSettings().
|
|
11
|
+
* There is a SettingsInput jsDoc type that allows all settings fields to be undefined.
|
|
12
|
+
* That way, an app can pass in a blank object and still get a header. This `defaultSettings`
|
|
13
|
+
* then are defaults for all the required fields in case they really did pass a blank object
|
|
14
|
+
* to setUtahHeaderSettings. So make sure out all the required fields in this object with sensible
|
|
15
|
+
* default values as a starting place for a new app.
|
|
16
|
+
* @type {Settings} the current settings of the header
|
|
17
|
+
*/
|
|
18
|
+
export default {
|
|
19
|
+
onSearch: false,
|
|
20
|
+
mainMenu: false,
|
|
21
|
+
mediaSizes: {
|
|
22
|
+
mobile: 640,
|
|
23
|
+
tabletPortrait: 768,
|
|
24
|
+
tabletLandscape: 1024,
|
|
25
|
+
},
|
|
26
|
+
showTitle: true,
|
|
27
|
+
size: sizes.MEDIUM,
|
|
28
|
+
title: 'My Utah.gov Site',
|
|
29
|
+
titleURL: '/',
|
|
30
|
+
utahId: false,
|
|
31
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import settingsKeeper from './settingsKeeper';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../misc/jsDocTypes').Settings} Settings
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* an easy to use function, this is what the world expects.
|
|
10
|
+
* the functional world isn't ready for objects. it tried.
|
|
11
|
+
* it didn't stick. back to functions.
|
|
12
|
+
* @returns {Settings} the current settings information
|
|
13
|
+
*/
|
|
14
|
+
export default function getUtahHeaderSettings() {
|
|
15
|
+
return settingsKeeper.getSettings();
|
|
16
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import events from '../enumerations/events';
|
|
3
|
+
import { loadHeader, removeHeader } from '../lifecycle/lifecycle';
|
|
4
|
+
import defaultSettings from './defaultSettings';
|
|
5
|
+
import settingsKeeper from './settingsKeeper';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {import('../misc/jsDocTypes').FooterSettings} FooterSettings
|
|
9
|
+
* @typedef {import('../misc/jsDocTypes').Settings} Settings
|
|
10
|
+
* @typedef {import('../misc/jsDocTypes').SettingsInput} SettingsInput
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function doLoadHeader() {
|
|
14
|
+
removeHeader(false);
|
|
15
|
+
|
|
16
|
+
loadHeader();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Trigger a custom event ('utahHeaderLoaded') that developers can listen for
|
|
20
|
+
// in their applications.
|
|
21
|
+
// The event needs to wait for the UMD library to load the global window.utahHeader
|
|
22
|
+
// module. Use setInterval to wait for this script to finish running before firing
|
|
23
|
+
// the `utahHeaderLoaded` event.
|
|
24
|
+
let isSetUtahHeaderSettingsCalled = false;
|
|
25
|
+
const MAX_EVENT_FIRES = 15000;
|
|
26
|
+
let numberEventFires = 0;
|
|
27
|
+
|
|
28
|
+
const intervalId = setInterval(
|
|
29
|
+
() => {
|
|
30
|
+
numberEventFires += 1;
|
|
31
|
+
if (numberEventFires >= MAX_EVENT_FIRES || isSetUtahHeaderSettingsCalled) {
|
|
32
|
+
clearInterval(intervalId);
|
|
33
|
+
} else {
|
|
34
|
+
// please, developer, call setUtahHeaderSettings() as soon as you receive this event... the header
|
|
35
|
+
// can't load if you don't give it any settings.
|
|
36
|
+
document.dispatchEvent(new Event(events.HEADER_LOADED));
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
2
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {SettingsInput} newSettings
|
|
44
|
+
* @returns {Settings}
|
|
45
|
+
*/
|
|
46
|
+
export function setUtahHeaderSettings(newSettings) {
|
|
47
|
+
// note that if newSettings has a key/value where the value is undefined it WILL override the value to undefined
|
|
48
|
+
// but if newSettings is missing a key then the `undefined` value of the missing key will not override the default.
|
|
49
|
+
// this is only a shallow copy, so merging nested settings does not happen.
|
|
50
|
+
settingsKeeper.setSettings(newSettings);
|
|
51
|
+
|
|
52
|
+
isSetUtahHeaderSettingsCalled = true;
|
|
53
|
+
|
|
54
|
+
if (document?.body) {
|
|
55
|
+
doLoadHeader();
|
|
56
|
+
} else {
|
|
57
|
+
window.addEventListener('load', () => doLoadHeader());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return settingsKeeper.getSettings();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {FooterSettings} [footerSettings]
|
|
65
|
+
* @returns {FooterSettings | undefined}
|
|
66
|
+
*/
|
|
67
|
+
export function setUtahFooterSettings(footerSettings) {
|
|
68
|
+
return setUtahHeaderSettings({ ...defaultSettings, ...settingsKeeper.getSettings(), footer: footerSettings });
|
|
69
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import defaultSettings from './defaultSettings';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('../misc/jsDocTypes').FooterSettings} FooterSettings
|
|
6
|
+
* @typedef {import('../misc/jsDocTypes').Settings} Settings
|
|
7
|
+
* @typedef {import('../misc/jsDocTypes').SettingsInput} SettingsInput
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Settings} settingsToValidate
|
|
12
|
+
*/
|
|
13
|
+
function validateSettings(settingsToValidate) {
|
|
14
|
+
if (!settingsToValidate.showTitle && !settingsToValidate.logo) {
|
|
15
|
+
throw new Error('validateSettings: A title must be shown if there is no logo. Please change the `showTitle` setting to be `true` or provide a logo image.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* settings had to be stored external to settings.js
|
|
21
|
+
* and they had to be global
|
|
22
|
+
* and they couldn't/shouldn't be put on the window variable
|
|
23
|
+
* so wrapped them in an class object
|
|
24
|
+
*/
|
|
25
|
+
class SettingsKeeper {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.settings = { ...defaultSettings };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {SettingsInput} settings
|
|
32
|
+
*/
|
|
33
|
+
setSettings(settings) {
|
|
34
|
+
const newSettings = { ...defaultSettings, ...this.settings, ...settings };
|
|
35
|
+
validateSettings(newSettings);
|
|
36
|
+
this.settings = newSettings;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @returns {Settings}
|
|
41
|
+
*/
|
|
42
|
+
getSettings() {
|
|
43
|
+
return this.settings;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default new SettingsKeeper();
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* An XMLHttpRequest that fires callback functions when the request is complete
|
|
4
|
+
* @param {Object} params
|
|
5
|
+
* @param {string} params.url - URL and parameters of the request
|
|
6
|
+
* @param {string} params.method - 'GET' or 'POST' - defaults to 'GET'
|
|
7
|
+
* @param {Object.<string, string>} params.headers - key:value pairs to set request headers
|
|
8
|
+
* @param {number} params.timeout - The timeout length
|
|
9
|
+
* @param {function} params.onResolve - Function to call on a successful request
|
|
10
|
+
* @param {function} params.onReject - Function to call on error or timeout
|
|
11
|
+
*/
|
|
12
|
+
export default function httpRequest({
|
|
13
|
+
url,
|
|
14
|
+
method,
|
|
15
|
+
headers,
|
|
16
|
+
timeout,
|
|
17
|
+
onResolve,
|
|
18
|
+
onReject,
|
|
19
|
+
}) {
|
|
20
|
+
// Create the XHR request
|
|
21
|
+
const request = new XMLHttpRequest();
|
|
22
|
+
|
|
23
|
+
request.addEventListener('load', (e) => onResolve(e.target));
|
|
24
|
+
request.addEventListener('error', (e) => onReject(e.target));
|
|
25
|
+
request.addEventListener('timeout', (e) => onReject(e.target));
|
|
26
|
+
|
|
27
|
+
// Setup our HTTP request
|
|
28
|
+
request.open(method || 'GET', url, true);
|
|
29
|
+
|
|
30
|
+
request.withCredentials = true;
|
|
31
|
+
|
|
32
|
+
if (timeout) {
|
|
33
|
+
request.timeout = timeout;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (headers) {
|
|
37
|
+
Object.keys(headers).forEach((key) => request.setRequestHeader(key, headers[key] || ''));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Send the request
|
|
41
|
+
request.send();
|
|
42
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import utahIdUrls from '../enumerations/utahIdUrls';
|
|
3
|
+
import { authChangedEventHandler } from '../renderables/utahId/UtahId';
|
|
4
|
+
import getUtahHeaderSettings from '../settings/getUtahHeaderSettings';
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {import('../misc/jsDocTypes').UtahIdData} UtahIdData
|
|
7
|
+
* @typedef {import('../misc/jsDocTypes').UtahIdFetchStyle} UtahIdFetchStyle
|
|
8
|
+
* @typedef {import('../misc/jsDocTypes').UtahIDSettings} UtahIDSettings
|
|
9
|
+
* @typedef {import('../misc/jsDocTypes').UserInfo} UserInfo
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** @enum {UtahIdFetchStyle} */
|
|
13
|
+
const UtahIdFetchStyle = {
|
|
14
|
+
AUTOMATIC: /** @type {UtahIdFetchStyle} */ ('Automatic'),
|
|
15
|
+
NONE: /** @type {UtahIdFetchStyle} */ ('None'),
|
|
16
|
+
PROVIDED: /** @type {UtahIdFetchStyle} */ ('Provided'),
|
|
17
|
+
};
|
|
18
|
+
let lastFetchStyle = UtahIdFetchStyle.NONE;
|
|
19
|
+
/**
|
|
20
|
+
* @param {UtahIDSettings | boolean | undefined} utahIdData
|
|
21
|
+
* @returns {UtahIdFetchStyle}
|
|
22
|
+
*/
|
|
23
|
+
function determineFetchStyle(utahIdData) {
|
|
24
|
+
/** @type {UtahIdFetchStyle} */
|
|
25
|
+
let fetchStyle;
|
|
26
|
+
if (utahIdData === true) {
|
|
27
|
+
fetchStyle = UtahIdFetchStyle.AUTOMATIC;
|
|
28
|
+
} else if (utahIdData === false) {
|
|
29
|
+
fetchStyle = UtahIdFetchStyle.NONE;
|
|
30
|
+
} else if (utahIdData === undefined || utahIdData.currentUser === undefined) {
|
|
31
|
+
fetchStyle = UtahIdFetchStyle.AUTOMATIC;
|
|
32
|
+
} else if (utahIdData.currentUser === null || utahIdData.currentUser) {
|
|
33
|
+
fetchStyle = UtahIdFetchStyle.PROVIDED;
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error('determineFetchStyle: Unknown utah id fetch style');
|
|
36
|
+
}
|
|
37
|
+
return fetchStyle;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @type {UtahIdData}
|
|
42
|
+
*/
|
|
43
|
+
const utahIdData = {
|
|
44
|
+
// null = not yet loaded, false = ajaxing, true = have a result (may be error or user data)
|
|
45
|
+
isDefinitive: null,
|
|
46
|
+
lastError: null,
|
|
47
|
+
userInfo: null,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* when auth status changes, call this to notify the world including the Sign In button
|
|
52
|
+
*
|
|
53
|
+
* @param {UtahIdData} newUtahIdData the current information to store
|
|
54
|
+
*/
|
|
55
|
+
function maybeTriggerAuthEvent(newUtahIdData) {
|
|
56
|
+
// something asked for new information, so fire off that new information has arrived
|
|
57
|
+
if (newUtahIdData.isDefinitive) {
|
|
58
|
+
// call auth changed so name updates in button
|
|
59
|
+
authChangedEventHandler(newUtahIdData);
|
|
60
|
+
|
|
61
|
+
// give settings callback a crack at the auth change
|
|
62
|
+
const utahId = getUtahHeaderSettings()?.utahId;
|
|
63
|
+
if (typeof utahId === 'object') {
|
|
64
|
+
utahId.onAuthChanged?.(newUtahIdData);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// give the application a bit of time to call setUtahHeaderSettings() so that they
|
|
70
|
+
// can tell the header if the application will be controlling the logged in user
|
|
71
|
+
// if the application controls the user, then the user is not fetched from UtahID
|
|
72
|
+
// within this "waitForLaunch" window, the application must call setUtahHeaderSettings()
|
|
73
|
+
// if the current user is not yet known, make sure to set `settings.utahId.currentUser = null`
|
|
74
|
+
// this way the header knows the user is controlled by the app and to not go fetch the user.
|
|
75
|
+
let waitingForLaunch = true;
|
|
76
|
+
const WAIT_FOR_LAUNCH_MS = 500;
|
|
77
|
+
|
|
78
|
+
/** @type {number} */
|
|
79
|
+
let fetchUserTimeoutId = NaN;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @returns {Promise<UtahIdData>}
|
|
83
|
+
*/
|
|
84
|
+
export async function fetchUtahIdUserDataAsync() {
|
|
85
|
+
/** @type {Promise<UtahIdData>} */
|
|
86
|
+
let result = Promise.resolve(utahIdData);
|
|
87
|
+
const settings = getUtahHeaderSettings();
|
|
88
|
+
const fetchStyle = determineFetchStyle(settings.utahId);
|
|
89
|
+
|
|
90
|
+
if (utahIdData.isDefinitive === false) {
|
|
91
|
+
// working on it... come back later...
|
|
92
|
+
result = Promise.resolve(utahIdData);
|
|
93
|
+
} else if (waitingForLaunch) {
|
|
94
|
+
clearTimeout(fetchUserTimeoutId);
|
|
95
|
+
result = new Promise((resolve) => {
|
|
96
|
+
fetchUserTimeoutId = window.setTimeout(
|
|
97
|
+
() => {
|
|
98
|
+
// if the app hasn't called setUtahHeaderSettings() by now, too bad for them...
|
|
99
|
+
waitingForLaunch = false;
|
|
100
|
+
fetchUtahIdUserDataAsync()
|
|
101
|
+
.then((data) => resolve(data))
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
.catch((e) => console.error(e));
|
|
104
|
+
},
|
|
105
|
+
WAIT_FOR_LAUNCH_MS
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
} else if (settings.utahId === false) {
|
|
109
|
+
// if utahId is set and currentUser is undefined then the header has control of the user
|
|
110
|
+
// otherwise, if utahId is false then it is turned off
|
|
111
|
+
// otherwise, if utahId is an object and currentUser is not undefined, then the application will control the current user
|
|
112
|
+
// utahId is turned off (probably shouldn't even have gotten here?)
|
|
113
|
+
result = Promise.resolve({
|
|
114
|
+
isDefinitive: true,
|
|
115
|
+
lastError: 'Utah ID is off',
|
|
116
|
+
userInfo: null,
|
|
117
|
+
});
|
|
118
|
+
} else if (settings.utahId === true || settings.utahId?.currentUser === undefined) {
|
|
119
|
+
// 👆 catches true && null cases, both of which allow a refetch 👆
|
|
120
|
+
|
|
121
|
+
// header is "on" OR utahId settings has an `undefined` user: Header controls the user!
|
|
122
|
+
if (fetchStyle !== lastFetchStyle || utahIdData.isDefinitive === null) {
|
|
123
|
+
utahIdData.isDefinitive = false;
|
|
124
|
+
result = fetch(utahIdUrls.USER_INFO, { credentials: 'include' })
|
|
125
|
+
.then((resp) => resp.json())
|
|
126
|
+
.then((authResult) => {
|
|
127
|
+
if (authResult.status === 200) {
|
|
128
|
+
utahIdData.lastError = null;
|
|
129
|
+
utahIdData.userInfo = /** @type {UserInfo} */ (authResult.data);
|
|
130
|
+
} else {
|
|
131
|
+
throw new Error(authResult.err);
|
|
132
|
+
}
|
|
133
|
+
return utahIdData;
|
|
134
|
+
})
|
|
135
|
+
.catch((error) => {
|
|
136
|
+
utahIdData.lastError = error;
|
|
137
|
+
utahIdData.userInfo = null;
|
|
138
|
+
return utahIdData;
|
|
139
|
+
})
|
|
140
|
+
.finally(() => {
|
|
141
|
+
utahIdData.isDefinitive = true;
|
|
142
|
+
maybeTriggerAuthEvent(utahIdData);
|
|
143
|
+
return utahIdData;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
// utahId settings have currentUser as null or a user, either way the application will be controlling the user, not the header
|
|
148
|
+
const resultData = {
|
|
149
|
+
isDefinitive: true,
|
|
150
|
+
lastError: null,
|
|
151
|
+
userInfo: settings.utahId?.currentUser,
|
|
152
|
+
};
|
|
153
|
+
result = Promise.resolve(resultData);
|
|
154
|
+
maybeTriggerAuthEvent(resultData);
|
|
155
|
+
}
|
|
156
|
+
lastFetchStyle = fetchStyle;
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @returns {UtahIdData}
|
|
162
|
+
*/
|
|
163
|
+
export function getCurrentUtahIdData() {
|
|
164
|
+
return utahIdData;
|
|
165
|
+
}
|