orc-shared 1.1.0-dev.9 → 1.1.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.
Files changed (259) hide show
  1. package/dist/actions/applications.js +1 -1
  2. package/dist/actions/authentication.js +1 -1
  3. package/dist/actions/countries.js +1 -1
  4. package/dist/actions/locale.js +1 -1
  5. package/dist/actions/makeApiAction.js +73 -3
  6. package/dist/actions/makeOrcApiAction.js +2 -2
  7. package/dist/actions/metadata.js +295 -31
  8. package/dist/actions/navigation.js +3 -3
  9. package/dist/actions/requestState.js +78 -0
  10. package/dist/actions/requestsApi.js +58 -7
  11. package/dist/actions/scopes.js +1 -1
  12. package/dist/actions/timezones.js +1 -1
  13. package/dist/actions/toasts.js +1 -1
  14. package/dist/actions/versionInfo.js +1 -1
  15. package/dist/actions/view.js +1 -1
  16. package/dist/buildStore.js +7 -4
  17. package/dist/components/AppFrame/About.js +3 -3
  18. package/dist/components/AppFrame/ApplicationSelector/ApplicationDialog.js +115 -62
  19. package/dist/components/AppFrame/ApplicationSelector/index.js +45 -47
  20. package/dist/components/AppFrame/MenuItem.js +1 -1
  21. package/dist/components/AppFrame/Preferences.js +3 -3
  22. package/dist/components/AppFrame/Sidebar.js +1 -1
  23. package/dist/components/AppFrame/Topbar.js +1 -1
  24. package/dist/components/Authenticate.js +1 -1
  25. package/dist/components/CategoryList.js +1 -1
  26. package/dist/components/Checkbox.js +1 -1
  27. package/dist/components/DropMenu/Menu.js +1 -1
  28. package/dist/components/DropMenu/index.js +1 -1
  29. package/dist/components/Form/FieldList.js +3 -3
  30. package/dist/components/Form/Form.js +1 -1
  31. package/dist/components/Form/Inputs/Button.js +1 -1
  32. package/dist/components/Form/Inputs/FieldButtons.js +1 -1
  33. package/dist/components/Form/Inputs/Number.js +1 -1
  34. package/dist/components/Form/Inputs/ReadOnly.js +1 -1
  35. package/dist/components/Form/Inputs/SmallButton.js +1 -1
  36. package/dist/components/Form/Inputs/Text.js +1 -1
  37. package/dist/components/Form/Inputs/Time.js +1 -1
  38. package/dist/components/Form/Inputs/Toggles.js +1 -1
  39. package/dist/components/Form/Inputs/Translation.js +3 -3
  40. package/dist/components/List/HeadCell.js +1 -1
  41. package/dist/components/List/List.js +1 -1
  42. package/dist/components/List/Row.js +1 -1
  43. package/dist/components/List/enhanceColumnDefs.js +2 -2
  44. package/dist/components/MaterialUI/DataDisplay/List.js +1 -1
  45. package/dist/components/MaterialUI/DataDisplay/Notification.js +2 -2
  46. package/dist/components/MaterialUI/DataDisplay/NotificationProps.js +1 -1
  47. package/dist/components/MaterialUI/DataDisplay/PredefinedElements/Translations.js +3 -3
  48. package/dist/components/MaterialUI/DataDisplay/SelectionList.js +19 -6
  49. package/dist/components/MaterialUI/DataDisplay/Table.js +28 -10
  50. package/dist/components/MaterialUI/DataDisplay/TableProps.js +5 -2
  51. package/dist/components/MaterialUI/DataDisplay/TransferList.js +5 -2
  52. package/dist/components/MaterialUI/DataDisplay/chipProps.js +1 -1
  53. package/dist/components/MaterialUI/DataDisplay/collapsableListProps.js +1 -1
  54. package/dist/components/MaterialUI/DataDisplay/dividerProps.js +1 -1
  55. package/dist/components/MaterialUI/DataDisplay/index.js +1 -1
  56. package/dist/components/MaterialUI/DataDisplay/modalProps.js +1 -1
  57. package/dist/components/MaterialUI/DataDisplay/useTableSelection.js +3 -3
  58. package/dist/components/MaterialUI/Feedback/useNotification.js +1 -1
  59. package/dist/components/MaterialUI/Inputs/Autocomplete.js +2 -2
  60. package/dist/components/MaterialUI/Inputs/AutocompleteProps.js +1 -1
  61. package/dist/components/MaterialUI/Inputs/CheckboxGroupProps.js +1 -1
  62. package/dist/components/MaterialUI/Inputs/CheckboxProps.js +1 -1
  63. package/dist/components/MaterialUI/Inputs/DatePicker.js +11 -0
  64. package/dist/components/MaterialUI/Inputs/InputBase.js +3 -2
  65. package/dist/components/MaterialUI/Inputs/InputBaseProps.js +1 -1
  66. package/dist/components/MaterialUI/Inputs/PredefinedElements/SearchControl.js +1 -1
  67. package/dist/components/MaterialUI/Inputs/RadioProps.js +1 -1
  68. package/dist/components/MaterialUI/Inputs/Select.js +2 -2
  69. package/dist/components/MaterialUI/Inputs/SelectProps.js +1 -1
  70. package/dist/components/MaterialUI/Inputs/Switch.js +3 -3
  71. package/dist/components/MaterialUI/Inputs/SwitchProps.js +1 -1
  72. package/dist/components/MaterialUI/Inputs/TimePicker.js +19 -8
  73. package/dist/components/MaterialUI/Inputs/createInput.js +3 -3
  74. package/dist/components/MaterialUI/Inputs/index.js +1 -1
  75. package/dist/components/MaterialUI/Inputs/standaloneRadioProps.js +1 -1
  76. package/dist/components/MaterialUI/Navigation/DropDownMenuProps.js +1 -1
  77. package/dist/components/MaterialUI/Navigation/TabBar.js +38 -33
  78. package/dist/components/MaterialUI/ScopeSelector/ScopeSelector.js +13 -5
  79. package/dist/components/MaterialUI/ScopeSelector/ScopeTreeView.js +8 -4
  80. package/dist/components/MaterialUI/ScopeSelector/TreeItem.js +9 -6
  81. package/dist/components/MaterialUI/Surfaces/ExpansionPanel.js +11 -2
  82. package/dist/components/MaterialUI/Surfaces/SectionExpansionPanel.js +10 -2
  83. package/dist/components/MaterialUI/Surfaces/expansionPanelProps.js +5 -2
  84. package/dist/components/MaterialUI/Surfaces/paperProps.js +1 -1
  85. package/dist/components/MaterialUI/muiThemes.js +21 -6
  86. package/dist/components/MaterialUI/textProps.js +1 -1
  87. package/dist/components/Modules.js +24 -2
  88. package/dist/components/MultiSelector.js +1 -1
  89. package/dist/components/Navigation/Bar.js +3 -3
  90. package/dist/components/Navigation/Tab.js +1 -1
  91. package/dist/components/Navigation/useNavigationState.js +30 -4
  92. package/dist/components/Placeholder.js +1 -1
  93. package/dist/components/Routing/FullPage.js +2 -0
  94. package/dist/components/Routing/Segment.js +4 -3
  95. package/dist/components/Routing/SegmentPage.js +8 -6
  96. package/dist/components/Routing/withWaypointing.js +10 -4
  97. package/dist/components/Scope/ScopeNode.js +1 -1
  98. package/dist/components/Scope/Selector.js +1 -1
  99. package/dist/components/Scope/index.js +11 -3
  100. package/dist/components/Scope/useScopeSelect.js +1 -1
  101. package/dist/components/Selector.js +1 -1
  102. package/dist/components/Sidepanel.js +1 -2
  103. package/dist/components/Spritesheet.js +1 -1
  104. package/dist/components/Switch.js +1 -1
  105. package/dist/components/Text.js +1 -1
  106. package/dist/components/ToastList.js +1 -1
  107. package/dist/components/Toolbar.js +1 -1
  108. package/dist/components/Treeview/Label.js +1 -1
  109. package/dist/components/Treeview/Leaf.js +1 -1
  110. package/dist/components/Treeview/Node.js +3 -3
  111. package/dist/components/Treeview/index.js +2 -2
  112. package/dist/components/Treeview/settings.js +1 -1
  113. package/dist/constants.js +26 -1
  114. package/dist/content/icons/html-templates.svg +3 -0
  115. package/dist/content/iconsSheet.svg +3 -0
  116. package/dist/getThemeOverrides.js +2 -2
  117. package/dist/hocs/withInfiniteScroll.js +1 -1
  118. package/dist/hocs/withUpdateHandler.js +2 -2
  119. package/dist/hooks/useDispatchWithModulesData.js +1 -1
  120. package/dist/hooks/useEditState.js +3 -3
  121. package/dist/hooks/useEntityLoader.js +182 -0
  122. package/dist/hooks/useFullEntityEditState.js +3 -3
  123. package/dist/hooks/useInfiniteScroll.js +104 -0
  124. package/dist/hooks/useLabelMessage.js +3 -3
  125. package/dist/hooks/useLoader.js +1 -0
  126. package/dist/hooks/useMultipleFieldEditState.js +2 -2
  127. package/dist/hooks/useNavigationHandler.js +1 -1
  128. package/dist/hooks/useNotificationRequestState.js +159 -0
  129. package/dist/hooks/useRequestState.js +146 -0
  130. package/dist/hooks/useSelectorAndUnwrap.js +1 -1
  131. package/dist/reducers/metadata.js +52 -26
  132. package/dist/reducers/requestStates.js +181 -0
  133. package/dist/schemas/countries.js +1 -1
  134. package/dist/schemas/definitions.js +1 -1
  135. package/dist/schemas/metadata.js +1 -1
  136. package/dist/schemas/productDefinitions.js +1 -1
  137. package/dist/schemas/timezones.js +1 -1
  138. package/dist/selectors/applications.js +1 -1
  139. package/dist/selectors/authentication.js +1 -2
  140. package/dist/selectors/countries.js +1 -1
  141. package/dist/selectors/locale.js +1 -1
  142. package/dist/selectors/metadata.js +117 -17
  143. package/dist/selectors/navigation.js +1 -1
  144. package/dist/selectors/requestStates.js +82 -0
  145. package/dist/selectors/requests.js +1 -1
  146. package/dist/selectors/scope.js +2 -1
  147. package/dist/selectors/versionInfo.js +1 -1
  148. package/dist/selectors/view.js +1 -1
  149. package/dist/sharedMessages.js +44 -0
  150. package/dist/spawnerMiddleware.js +1 -1
  151. package/dist/utils/displayModeHelper.js +1 -1
  152. package/dist/utils/flatten.js +2 -2
  153. package/dist/utils/localizationHelper.js +1 -1
  154. package/dist/utils/mapHelper.js +1 -1
  155. package/dist/utils/modelValidationHelper.js +2 -2
  156. package/dist/utils/parseHelper.js +1 -1
  157. package/dist/utils/propertyHelper.js +2 -2
  158. package/dist/utils/propertyValidator.js +1 -1
  159. package/dist/utils/setTranslation.js +27 -1
  160. package/dist/utils/setTranslationWithFallback.js +33 -3
  161. package/dist/utils/testUtils.js +6 -2
  162. package/dist/utils/timezoneHelper.js +111 -0
  163. package/dist/utils/unwrapImmutable.js +1 -1
  164. package/dist/utils/urlHelper.js +11 -1
  165. package/package.json +3 -7
  166. package/src/actions/makeApiAction.js +24 -1
  167. package/src/actions/makeApiAction.test.js +76 -3
  168. package/src/actions/metadata.js +130 -0
  169. package/src/actions/metadata.test.js +337 -5
  170. package/src/actions/requestState.js +8 -0
  171. package/src/actions/requestState.test.js +14 -0
  172. package/src/actions/requestsApi.js +30 -0
  173. package/src/buildStore.js +2 -0
  174. package/src/components/AppFrame/AppFrame.test.js +6 -16
  175. package/src/components/AppFrame/ApplicationSelector/ApplicationDialog.js +105 -82
  176. package/src/components/AppFrame/ApplicationSelector/ApplicationDialog.test.js +60 -23
  177. package/src/components/AppFrame/ApplicationSelector/ApplicationSelector.test.js +22 -89
  178. package/src/components/AppFrame/ApplicationSelector/index.js +34 -15
  179. package/src/components/AppFrame/Topbar.test.js +2 -4
  180. package/src/components/MaterialUI/DataDisplay/SelectionList.js +14 -6
  181. package/src/components/MaterialUI/DataDisplay/SelectionList.test.js +21 -11
  182. package/src/components/MaterialUI/DataDisplay/Table.js +29 -5
  183. package/src/components/MaterialUI/DataDisplay/Table.test.js +23 -0
  184. package/src/components/MaterialUI/DataDisplay/TableProps.js +2 -0
  185. package/src/components/MaterialUI/DataDisplay/TableProps.test.js +2 -0
  186. package/src/components/MaterialUI/DataDisplay/TransferList.js +3 -0
  187. package/src/components/MaterialUI/Inputs/DatePicker.js +11 -0
  188. package/src/components/MaterialUI/Inputs/InputBase.js +3 -1
  189. package/src/components/MaterialUI/Inputs/InputBase.test.js +38 -0
  190. package/src/components/MaterialUI/Inputs/TimePicker.js +9 -3
  191. package/src/components/MaterialUI/Inputs/TimePicker.test.js +263 -118
  192. package/src/components/MaterialUI/Navigation/TabBar.js +82 -78
  193. package/src/components/MaterialUI/Navigation/TabBar.test.js +129 -3
  194. package/src/components/MaterialUI/ScopeSelector/ScopeSelector.js +9 -3
  195. package/src/components/MaterialUI/ScopeSelector/ScopeSelector.test.js +29 -0
  196. package/src/components/MaterialUI/ScopeSelector/ScopeTreeView.js +4 -1
  197. package/src/components/MaterialUI/ScopeSelector/ScopeTreeView.test.js +52 -0
  198. package/src/components/MaterialUI/ScopeSelector/TreeItem.js +9 -6
  199. package/src/components/MaterialUI/ScopeSelector/TreeItem.test.js +63 -2
  200. package/src/components/MaterialUI/Surfaces/ExpansionPanel.js +14 -1
  201. package/src/components/MaterialUI/Surfaces/ExpansionPanel.test.js +16 -0
  202. package/src/components/MaterialUI/Surfaces/SectionExpansionPanel.js +11 -2
  203. package/src/components/MaterialUI/Surfaces/SectionExpansionPanel.test.js +36 -0
  204. package/src/components/MaterialUI/Surfaces/expansionPanelProps.js +2 -0
  205. package/src/components/MaterialUI/Surfaces/expansionPanelProps.test.js +2 -2
  206. package/src/components/MaterialUI/muiThemes.js +18 -3
  207. package/src/components/Modules.js +13 -1
  208. package/src/components/Modules.test.js +133 -1
  209. package/src/components/Navigation/Navigation.test.js +2 -0
  210. package/src/components/Navigation/useNavigationState.js +21 -1
  211. package/src/components/Navigation/useNavigationState.test.js +10 -0
  212. package/src/components/Routing/FullPage.js +2 -1
  213. package/src/components/Routing/FullPage.test.js +52 -0
  214. package/src/components/Routing/Segment.js +5 -2
  215. package/src/components/Routing/Segment.test.js +22 -1
  216. package/src/components/Routing/SegmentPage.js +12 -4
  217. package/src/components/Routing/SubPage.test.js +1 -1
  218. package/src/components/Routing/withWaypointing.js +19 -17
  219. package/src/components/Routing/withWaypointing.test.js +50 -0
  220. package/src/components/Scope/Scope.test.js +117 -0
  221. package/src/components/Scope/index.js +6 -10
  222. package/src/components/Sidepanel.js +0 -1
  223. package/src/components/Sidepanel.test.js +0 -3
  224. package/src/constants.js +18 -0
  225. package/src/content/icons/html-templates.svg +3 -0
  226. package/src/content/iconsSheet.svg +3 -0
  227. package/src/hooks/useEntityLoader.js +68 -0
  228. package/src/hooks/useEntityLoader.test.js +266 -0
  229. package/src/hooks/useInfiniteScroll.js +25 -0
  230. package/src/hooks/useInfiniteScroll.test.js +87 -0
  231. package/src/hooks/useLoader.js +1 -0
  232. package/src/hooks/useNotificationRequestState.js +78 -0
  233. package/src/hooks/useNotificationRequestState.test.js +264 -0
  234. package/src/hooks/useRequestState.js +57 -0
  235. package/src/hooks/useRequestState.test.js +217 -0
  236. package/src/reducers/metadata.js +76 -16
  237. package/src/reducers/metadata.test.js +458 -4
  238. package/src/reducers/requestStates.js +98 -0
  239. package/src/reducers/requestStates.test.js +399 -0
  240. package/src/selectors/authentication.js +0 -1
  241. package/src/selectors/locale.test.js +0 -2
  242. package/src/selectors/metadata.js +90 -19
  243. package/src/selectors/metadata.test.js +532 -123
  244. package/src/selectors/requestStates.js +12 -0
  245. package/src/selectors/requestStates.test.js +83 -0
  246. package/src/selectors/scope.js +1 -1
  247. package/src/sharedMessages.js +44 -0
  248. package/src/translations/en-US.json +12 -1
  249. package/src/translations/fr-CA.json +13 -2
  250. package/src/utils/modelValidationHelper.js +1 -1
  251. package/src/utils/setTranslation.js +16 -1
  252. package/src/utils/setTranslation.test.js +24 -0
  253. package/src/utils/setTranslationWithFallback.js +19 -2
  254. package/src/utils/setTranslationWithFallback.test.js +104 -6
  255. package/src/utils/testUtils.js +3 -1
  256. package/src/utils/timezoneHelper.js +140 -0
  257. package/src/utils/timezoneHelper.test.js +33 -0
  258. package/src/utils/urlHelper.js +6 -0
  259. package/src/translations/it-IT.json +0 -54
@@ -16,7 +16,7 @@ import { isScrollVisible } from "./../../../utils/domHelper";
16
16
  import { getModifiedTabs, getTabsWithErrors } from "./../../../selectors/view";
17
17
  import ConfirmationModal from "./../DataDisplay/PredefinedElements/ConfirmationModal";
18
18
  import { removeEditNode } from "./../../../actions/view";
19
- import { getValueFromUrlByKey, tryGetNewEntityIdKey } from "./../../../utils/urlHelper";
19
+ import { tryGetNewEntityIdKey, resolveEntityId } from "./../../../utils/urlHelper";
20
20
  import { useDispatchWithModulesData } from "./../../../hooks/useDispatchWithModulesData";
21
21
  import sharedMessages from "./../../../sharedMessages";
22
22
  import { setClosingTabHandlerActions } from "../../../actions/navigation";
@@ -147,21 +147,15 @@ const MuiBar = ({ module, moduleName, pages }) => {
147
147
  return newIndex;
148
148
  };
149
149
 
150
- const tabCloseHandler = (event, closeCallback, isModified, href, path, entityIdKey, index) => {
150
+ const tabCloseHandler = (event, closeCallback, isModified, href, path, entityIdKey, entityId, index) => {
151
151
  event.stopPropagation();
152
152
  event.preventDefault();
153
153
  if (isModified) {
154
- setCurrentCloseData({
155
- closeCallback: closeCallback,
156
- href: href,
157
- path: path,
158
- entityIdKey: entityIdKey,
159
- index: index,
160
- });
154
+ setCurrentCloseData({ closeCallback, entityId, index });
161
155
  setShowConfirmationModal(true);
162
156
  } else {
163
157
  closeCallback();
164
- removeEditState(href, entityIdKey, path);
158
+ dispatch(removeEditNode, [entityId]);
165
159
  handleChange(null, getNewIndex(index));
166
160
  }
167
161
  };
@@ -169,17 +163,10 @@ const MuiBar = ({ module, moduleName, pages }) => {
169
163
  const closeTab = () => {
170
164
  setShowConfirmationModal(false);
171
165
  currentCloseData.closeCallback();
172
- removeEditState(currentCloseData.href, currentCloseData.entityIdKey, currentCloseData.path);
166
+ dispatch(removeEditNode, [currentCloseData.entityId]);
173
167
  handleChange(null, getNewIndex(currentCloseData.index));
174
168
  };
175
169
 
176
- const removeEditState = (href, entityIdKey, path) => {
177
- let newKey = tryGetNewEntityIdKey(href);
178
- const key = entityIdKey === newKey ? entityIdKey : `:${entityIdKey}`;
179
- const entityId = getValueFromUrlByKey(href, path, key);
180
- dispatch(removeEditNode, [entityId]);
181
- };
182
-
183
170
  const moduleIcon = <Icon id={module.icon} className={classes.moduleIcon} />;
184
171
 
185
172
  const resizeHandler = React.useCallback(() => {
@@ -191,20 +178,9 @@ const MuiBar = ({ module, moduleName, pages }) => {
191
178
  resizeHandler();
192
179
  }, [resizeHandler, module, pages]);
193
180
 
194
- React.useEffect(() => {
195
- if (module.closingTabHandler?.entitySelector) {
196
- const actions = pages
197
- .map(p => ({
198
- entityId: module.closingTabHandler.entitySelector(p.params)?.entityId ?? null,
199
- closeTab: p.close,
200
- }))
201
- .filter(x => x.entityId != null);
202
-
203
- dispatchRedux(setClosingTabHandlerActions(moduleName, actions));
204
- }
205
- }, [dispatchRedux, moduleName, pages, module.closingTabHandler]);
181
+ const closingActions = [];
206
182
 
207
- return (
183
+ const allTabs = (
208
184
  <div className={classes.container}>
209
185
  <ResizeDetector onResize={resizeHandler} />
210
186
  <Tab
@@ -227,54 +203,76 @@ const MuiBar = ({ module, moduleName, pages }) => {
227
203
  }}
228
204
  ref={tabs}
229
205
  >
230
- {pages.map(({ href, label, outsideScope, close, path, params, mustTruncate, icon, isDetails }, index) => {
231
- let entityIdKey = Object.keys(params).find(p => p.toLowerCase().endsWith("id"));
232
- if (!entityIdKey) entityIdKey = tryGetNewEntityIdKey(href);
233
- const isModified = modifiedTabs.includes(href);
234
- const isError = tabsWithErrors.includes(href);
235
- const tabLabel = <TabLabel label={label} />;
236
- const sectionIconClss = classNames(classes.moduleIcon, isDetails && classes.sectionIcon);
237
- const tabClassName = classNames(
238
- classes.labelContainer,
239
- isModified && classes.modifiedLabel,
240
- isError && classes.errorLabel,
241
- );
242
- const sectionIcon = icon && <Icon id={icon} className={sectionIconClss} />;
206
+ {pages.map(
207
+ (
208
+ { href, label, outsideScope, close, path, params, mustTruncate, icon, isDetails, entityIdResolver },
209
+ index,
210
+ ) => {
211
+ let entityIdKey = Object.keys(params).find(p => p.toLowerCase().endsWith("id"));
212
+ if (!entityIdKey) entityIdKey = tryGetNewEntityIdKey(href);
213
+ const entityId =
214
+ typeof entityIdResolver === "function"
215
+ ? entityIdResolver({ match: { params } })
216
+ : resolveEntityId(href, path, entityIdKey);
217
+ const isModified = modifiedTabs.includes(href);
218
+ const isError = tabsWithErrors.includes(href);
219
+ const tabLabel = <TabLabel label={label} />;
220
+ const sectionIconClss = classNames(classes.moduleIcon, isDetails && classes.sectionIcon);
221
+ const tabClassName = classNames(
222
+ classes.labelContainer,
223
+ isModified && classes.modifiedLabel,
224
+ isError && classes.errorLabel,
225
+ );
226
+ const sectionIcon = icon && <Icon id={icon} className={sectionIconClss} />;
243
227
 
244
- const wrappedTabLabel = (
245
- <div className={tabClassName}>
246
- <TabLabel label={label} mustTruncate={mustTruncate} classes={{ root: tabClassName }} />
247
- {isModified === true ? <span className={classes.asterix}>*</span> : null}
248
- </div>
249
- );
250
- const closeIcon = (
251
- <Icon
252
- id="close"
253
- className={classes.closeIcon}
254
- onClick={event => tabCloseHandler(event, close, isModified, href, path, entityIdKey, index)}
255
- />
256
- );
257
- tabLabels.push({
258
- value: index,
259
- label: tabLabel,
260
- sortOrder: index,
261
- });
262
- return (
263
- <Tab
264
- classes={{
265
- root: classNames(classes.tab, !mustTruncate && classes.unsetMaxWidth),
266
- }}
267
- component={TabLink}
268
- label={wrappedTabLabel}
269
- icon={sectionIcon}
270
- key={href}
271
- to={href}
272
- value={index}
273
- close={closeIcon}
274
- disabled={outsideScope}
275
- />
276
- );
277
- })}
228
+ const wrappedTabLabel = (
229
+ <div className={tabClassName}>
230
+ <TabLabel label={label} mustTruncate={mustTruncate} classes={{ root: tabClassName }} />
231
+ {isModified === true ? <span className={classes.asterix}>*</span> : null}
232
+ </div>
233
+ );
234
+ const closeIcon = (
235
+ <Icon
236
+ id="close"
237
+ className={classes.closeIcon}
238
+ onClick={event => tabCloseHandler(event, close, isModified, href, path, entityIdKey, entityId, index)}
239
+ />
240
+ );
241
+ tabLabels.push({
242
+ value: index,
243
+ label: tabLabel,
244
+ sortOrder: index,
245
+ });
246
+
247
+ if (entityId != null) {
248
+ closingActions.push({
249
+ entityId: entityId,
250
+ closeTab: (event, executeHandlerOnly = false) => {
251
+ close(event, executeHandlerOnly);
252
+ if (executeHandlerOnly === false) {
253
+ handleChange(null, getNewIndex(index));
254
+ }
255
+ },
256
+ });
257
+ }
258
+
259
+ return (
260
+ <Tab
261
+ classes={{
262
+ root: classNames(classes.tab, !mustTruncate && classes.unsetMaxWidth),
263
+ }}
264
+ component={TabLink}
265
+ label={wrappedTabLabel}
266
+ icon={sectionIcon}
267
+ key={href}
268
+ to={href}
269
+ value={index}
270
+ close={closeIcon}
271
+ disabled={outsideScope}
272
+ />
273
+ );
274
+ },
275
+ )}
278
276
  </Tabs>
279
277
  {showSelect ? select : null}
280
278
  <ConfirmationModal
@@ -286,6 +284,12 @@ const MuiBar = ({ module, moduleName, pages }) => {
286
284
  />
287
285
  </div>
288
286
  );
287
+
288
+ React.useEffect(() => {
289
+ dispatchRedux(setClosingTabHandlerActions(moduleName, closingActions));
290
+ }, [dispatchRedux, moduleName, closingActions]);
291
+
292
+ return allTabs;
289
293
  };
290
294
 
291
295
  export default MuiBar;
@@ -79,7 +79,7 @@ describe("TabBar", () => {
79
79
  path: "/:scope/module1/:someParamPath/:someEntityId",
80
80
  },
81
81
  {
82
- label: messages.pageNew,
82
+ label: messages.page6,
83
83
  href: "/Scope1/module1/new",
84
84
  params: { scope: "Scope1" },
85
85
  active: false,
@@ -312,6 +312,7 @@ describe("TabBar", () => {
312
312
  );
313
313
 
314
314
  beforeEach(() => {
315
+ pages[2].entityIdResolver = undefined;
315
316
  module.closingTabHandler = null;
316
317
 
317
318
  state = Immutable.fromJS({
@@ -371,6 +372,19 @@ describe("TabBar", () => {
371
372
  getState: () => state,
372
373
  dispatch: () => {},
373
374
  };
375
+
376
+ pages[3] = {
377
+ label: messages.page4,
378
+ href: "/Scope1/module1/aParamPath/page4",
379
+ params: { scope: "Scope1", someParamPath: "aParamPath", someEntityId: "page4" },
380
+ active: false,
381
+ close: sinon.spy(),
382
+ path: "/:scope/module1/:someParamPath/:someEntityId",
383
+ };
384
+ });
385
+
386
+ beforeEach(() => {
387
+ history.push.resetHistory();
374
388
  });
375
389
 
376
390
  const theme = createMuiTheme();
@@ -411,12 +425,15 @@ describe("TabBar", () => {
411
425
  it("Renders TabBar correctly with closing tab handler", () => {
412
426
  store.dispatch = sinon.spy().named("dispatch");
413
427
 
428
+ pages[2].entityIdResolver = () => null;
429
+
414
430
  const component = (
415
431
  <TestWrapper
416
432
  provider={{ store }}
417
433
  memoryRouter
418
434
  intlProvider={{ messages }}
419
435
  stylesProvider
436
+ router={{ history }}
420
437
  muiThemeProvider={{ theme }}
421
438
  >
422
439
  <TabBar module={module} moduleName={"module1"} pages={pages} />
@@ -434,6 +451,7 @@ describe("TabBar", () => {
434
451
  memoryRouter
435
452
  intlProvider={{ messages }}
436
453
  stylesProvider
454
+ router={{ history }}
437
455
  muiThemeProvider={{ theme }}
438
456
  >
439
457
  <div>
@@ -454,6 +472,80 @@ describe("TabBar", () => {
454
472
  expect(store.dispatch.getCall(0).args[0].payload.actions.length, "to equal", 5);
455
473
  });
456
474
 
475
+ it("Close the tab correctly using closing tab handler actions", () => {
476
+ store.dispatch = sinon.spy().named("dispatch");
477
+
478
+ pages[2].entityIdResolver = () => null;
479
+
480
+ const component = (
481
+ <TestWrapper
482
+ provider={{ store }}
483
+ intlProvider={{ messages }}
484
+ stylesProvider
485
+ router={{ history }}
486
+ muiThemeProvider={{ theme }}
487
+ >
488
+ <TabBar module={module} moduleName={"module1"} pages={pages} />
489
+ </TestWrapper>
490
+ );
491
+
492
+ module.closingTabHandler = {
493
+ handler: () => {},
494
+ entitySelector: params => (params?.entityId !== "page3" ? { entityId: "entityId", entity: {} } : null),
495
+ };
496
+
497
+ mount(component);
498
+
499
+ expect(store.dispatch, "was called once");
500
+ expect(store.dispatch.getCall(0).args.length, "to equal", 1);
501
+ expect(store.dispatch.getCall(0).args[0].type, "to equal", SET_CLOSING_TAB_HANDLER_ACTIONS);
502
+ expect(store.dispatch.getCall(0).args[0].payload.module, "to equal", "module1");
503
+ expect(store.dispatch.getCall(0).args[0].payload.actions.length, "to equal", 5);
504
+
505
+ store.dispatch.getCall(0).args[0].payload.actions[1].closeTab({});
506
+
507
+ expect(pages[1].close, "was called");
508
+
509
+ expect(history.push, "to have a call satisfying", { args: [pages[2].href] });
510
+ });
511
+
512
+ it("Close the tab correctly using closing tab handler actions without affecting routing", () => {
513
+ store.dispatch = sinon.spy().named("dispatch");
514
+
515
+ pages[2].entityIdResolver = () => null;
516
+
517
+ const component = (
518
+ <TestWrapper
519
+ provider={{ store }}
520
+ intlProvider={{ messages }}
521
+ stylesProvider
522
+ router={{ history }}
523
+ muiThemeProvider={{ theme }}
524
+ >
525
+ <TabBar module={module} moduleName={"module1"} pages={pages} />
526
+ </TestWrapper>
527
+ );
528
+
529
+ module.closingTabHandler = {
530
+ handler: () => {},
531
+ entitySelector: params => (params?.entityId !== "page3" ? { entityId: "entityId", entity: {} } : null),
532
+ };
533
+
534
+ mount(component);
535
+
536
+ expect(store.dispatch, "was called once");
537
+ expect(store.dispatch.getCall(0).args.length, "to equal", 1);
538
+ expect(store.dispatch.getCall(0).args[0].type, "to equal", SET_CLOSING_TAB_HANDLER_ACTIONS);
539
+ expect(store.dispatch.getCall(0).args[0].payload.module, "to equal", "module1");
540
+ expect(store.dispatch.getCall(0).args[0].payload.actions.length, "to equal", 5);
541
+
542
+ store.dispatch.getCall(0).args[0].payload.actions[1].closeTab({}, true);
543
+
544
+ expect(pages[1].close, "was called");
545
+
546
+ expect(history.push, "was not called");
547
+ });
548
+
457
549
  it("Contains proper Select and Modal elements when they are visible", () => {
458
550
  const component = (
459
551
  <TestWrapper
@@ -594,6 +686,40 @@ describe("TabBar", () => {
594
686
  useDispatchWithModulesDataStub.restore();
595
687
  });
596
688
 
689
+ it("Calls correct close callback when close icon for specific page tab is clicked with a custom entity resolver", () => {
690
+ pages[3].entityIdResolver = () => "someCustomId";
691
+
692
+ const component = (
693
+ <TestWrapper
694
+ provider={{ store }}
695
+ router={{ history }}
696
+ intlProvider={{ messages }}
697
+ stylesProvider
698
+ muiThemeProvider={{ theme }}
699
+ >
700
+ <TabBar module={module} pages={pages} />
701
+ </TestWrapper>
702
+ );
703
+
704
+ const dispatchSpy = sinon.spy();
705
+ const useDispatchWithModulesDataStub = sinon
706
+ .stub(useDispatchWithModulesData, "useDispatchWithModulesData")
707
+ .returns(dispatchSpy);
708
+
709
+ const mountedComponent = mount(component);
710
+
711
+ const pageTab = mountedComponent.find(Tab).at(4);
712
+ const closeIcon = pageTab.find(Icon);
713
+
714
+ closeIcon.simulate("click");
715
+
716
+ expect(pages[3].close, "was called");
717
+
718
+ expect(dispatchSpy, "to have a call satisfying", { args: [removeEditNode, ["someCustomId"]] });
719
+
720
+ useDispatchWithModulesDataStub.restore();
721
+ });
722
+
597
723
  it("Calls correct close callback when close icon for specific page tab with multiple params is clicked and tab was not modified", () => {
598
724
  const component = (
599
725
  <TestWrapper
@@ -650,7 +776,7 @@ describe("TabBar", () => {
650
776
 
651
777
  closeIcon.simulate("click");
652
778
 
653
- expect(setState, "to have a call satisfying", { args: [{ closeCallback: pages[2].close, href: pages[2].href }] });
779
+ expect(setState, "to have a call satisfying", { args: [{ closeCallback: pages[2].close }] });
654
780
 
655
781
  expect(setState, "to have a call satisfying", { args: [true] });
656
782
  });
@@ -679,7 +805,7 @@ describe("TabBar", () => {
679
805
 
680
806
  closeIcon.simulate("click");
681
807
 
682
- expect(setState, "to have a call satisfying", { args: [{ closeCallback: pages[4].close, href: pages[4].href }] });
808
+ expect(setState, "to have a call satisfying", { args: [{ closeCallback: pages[4].close }] });
683
809
 
684
810
  expect(setState, "to have a call satisfying", { args: [true] });
685
811
  });
@@ -15,6 +15,11 @@ const useStyles = makeStyles(theme => ({
15
15
  backgroundColor: theme.palette.grey.light,
16
16
  border: `1px solid ${theme.palette.grey.borders}`,
17
17
  boxShadow: "0 2px 4px rgba(0,0,0,0.5)",
18
+ width: theme.spacing(50),
19
+ display: "flex",
20
+ },
21
+ scopeContainer: {
22
+ width: "100%",
18
23
  },
19
24
  scopeSelector: {
20
25
  display: "flex",
@@ -30,7 +35,7 @@ const useStyles = makeStyles(theme => ({
30
35
  },
31
36
  }));
32
37
 
33
- const ScopeSelector = ({ show, getScope, selectedScope, closeSelector, filter, updateFilter }) => {
38
+ const ScopeSelector = ({ show, getScope, selectedScope, closeSelector, filter, updateFilter, isScopeSelectable }) => {
34
39
  const classes = useStyles();
35
40
  const { formatMessage } = useIntl();
36
41
 
@@ -53,16 +58,17 @@ const ScopeSelector = ({ show, getScope, selectedScope, closeSelector, filter, u
53
58
  selected={selectedScope.id}
54
59
  expanded={selectedScope.scopePath}
55
60
  onSelected={closeSelector}
61
+ isScopeSelectable={isScopeSelectable}
56
62
  />
57
63
  </div>
58
64
  </>
59
65
  );
60
66
 
61
67
  const scopeSelector = (
62
- <Sidepanel className={classes.container} in={show} width="27vw" timeout={300}>
68
+ <Sidepanel className={classes.container} in={show} timeout={300}>
63
69
  <ClickAwayListener onClickAway={e => closeSelector(e)}>
64
70
  {/* this div is required since ClickAwayListener child element should be able to hold ref */}
65
- <div>{show ? scopeSelectorContent : null}</div>
71
+ <div className={classes.scopeContainer}>{show ? scopeSelectorContent : null}</div>
66
72
  </ClickAwayListener>
67
73
  </Sidepanel>
68
74
  );
@@ -138,4 +138,33 @@ describe("ScopeSelected", () => {
138
138
 
139
139
  expect(closeSelector, "was called");
140
140
  });
141
+
142
+ it("isScopeSelectable is passed to ScopeTreeView", () => {
143
+ const fn = sinon.spy().named("isScopeSelectable");
144
+ const component = (
145
+ <TestWrapper
146
+ provider={{ store }}
147
+ memoryRouter
148
+ intlProvider={{ messages }}
149
+ stylesProvider
150
+ muiThemeProvider={{ theme }}
151
+ >
152
+ <ScopeSelector
153
+ show={true}
154
+ closeSelector={closeSelector}
155
+ getScope={getScope}
156
+ selectedScope={selectedScope}
157
+ filter={filter}
158
+ updateFilter={updateFilter}
159
+ isScopeSelectable={fn}
160
+ />
161
+ </TestWrapper>
162
+ );
163
+
164
+ const mountedComponent = mount(component);
165
+
166
+ const treeView = mountedComponent.find(ScopeTreeView);
167
+
168
+ expect(treeView.props().isScopeSelectable, "to be", fn);
169
+ });
141
170
  });
@@ -10,6 +10,7 @@ export const BaseScopeTreeView = ({
10
10
  selected,
11
11
  setSelected,
12
12
  multipleSelect,
13
+ isScopeSelectable,
13
14
  }) => {
14
15
  const [expanded, setExpanded] = React.useState(initExpanded);
15
16
 
@@ -38,6 +39,7 @@ export const BaseScopeTreeView = ({
38
39
  scope={currentScope}
39
40
  rootId={rootId}
40
41
  onScopeSelect={onScopeSelect}
42
+ isScopeSelectable={isScopeSelectable}
41
43
  >
42
44
  {currentScope.children.map(child => renderTree(child))}
43
45
  </TreeItem>
@@ -51,7 +53,7 @@ export const BaseScopeTreeView = ({
51
53
  );
52
54
  };
53
55
 
54
- const ScopeTreeView = ({ rootId, getScope, selected, expanded, onSelected, multipleSelect }) => {
56
+ const ScopeTreeView = ({ rootId, getScope, selected, expanded, onSelected, multipleSelect, isScopeSelectable }) => {
55
57
  const initialSelected = Array.isArray(selected) ? selected : selected ? [selected] : [];
56
58
  const [currentSelected, setCurrentSelected] = React.useState(initialSelected);
57
59
 
@@ -64,6 +66,7 @@ const ScopeTreeView = ({ rootId, getScope, selected, expanded, onSelected, multi
64
66
  selected={currentSelected}
65
67
  setSelected={setCurrentSelected}
66
68
  multipleSelect={multipleSelect}
69
+ isScopeSelectable={isScopeSelectable}
67
70
  />
68
71
  );
69
72
  };
@@ -223,4 +223,56 @@ describe("ScopeTreeView", () => {
223
223
 
224
224
  expect(onSelectedSpy, "to have calls satisfying", [{ args: [event, scopes[3].id] }]);
225
225
  });
226
+
227
+ it("Selection is ignored when isScopeSelectable is defined and returns false", () => {
228
+ const isScopeSelectableSpy = sinon.spy(() => false).named("isScopeSelectable");
229
+ const component = (
230
+ <TestWrapper provider={{ store }} memoryRouter stylesProvider muiThemeProvider={{ theme }}>
231
+ <ScopeTreeView
232
+ rootId={scopes[0].id}
233
+ getScope={getScope}
234
+ selected={scopes[1].id}
235
+ expanded={scopes[1].scopePath}
236
+ onSelected={onSelectedSpy}
237
+ isScopeSelectable={isScopeSelectableSpy}
238
+ />
239
+ </TestWrapper>
240
+ );
241
+
242
+ const mountedComponent = mount(component);
243
+ let treeView = mountedComponent.find(TreeViewMui);
244
+ let item = treeView.find(TreeItemMui).at(2);
245
+
246
+ const event = { preventDefault: sinon.spy() };
247
+
248
+ item.invoke("onLabelClick")(event);
249
+
250
+ expect(onSelectedSpy, "was not called");
251
+ });
252
+
253
+ it("Selection is accepted when isScopeSelectable is defined and returns true", () => {
254
+ const isScopeSelectableSpy = sinon.spy(() => true).named("isScopeSelectable");
255
+ const component = (
256
+ <TestWrapper provider={{ store }} memoryRouter stylesProvider muiThemeProvider={{ theme }}>
257
+ <ScopeTreeView
258
+ rootId={scopes[0].id}
259
+ getScope={getScope}
260
+ selected={scopes[1].id}
261
+ expanded={scopes[1].scopePath}
262
+ onSelected={onSelectedSpy}
263
+ isScopeSelectable={isScopeSelectableSpy}
264
+ />
265
+ </TestWrapper>
266
+ );
267
+
268
+ const mountedComponent = mount(component);
269
+ let treeView = mountedComponent.find(TreeViewMui);
270
+ let item = treeView.find(TreeItemMui).at(2);
271
+
272
+ const event = { preventDefault: sinon.spy() };
273
+
274
+ item.invoke("onLabelClick")(event);
275
+
276
+ expect(onSelectedSpy, "to have calls satisfying", [{ args: [event, scopes[3].id] }]);
277
+ });
226
278
  });
@@ -25,8 +25,9 @@ const useStyles = makeStyles(theme => ({
25
25
  // },
26
26
  // }
27
27
  },
28
- globalIconContainer: {
28
+ rootIconContainer: {
29
29
  marginRight: 0,
30
+ display: "none",
30
31
  },
31
32
  scopeLabel: {
32
33
  position: "relative",
@@ -124,10 +125,11 @@ export const ScopeLabel = ({ name, type, isRootScope, hasChildren, isVirtualScop
124
125
  return label;
125
126
  };
126
127
 
127
- const TreeItem = ({ scope, rootId, onScopeSelect, children }) => {
128
+ const TreeItem = ({ scope, rootId, onScopeSelect, isScopeSelectable, children }) => {
128
129
  const isRootScope = scope.id === rootId;
129
130
  const isVirtualScope = scope.type === scopeTypes.virtual;
130
131
  const hasChildren = scope.children.length > 0;
132
+ const isSelectable = !isVirtualScope && (!isScopeSelectable || isScopeSelectable(scope.id));
131
133
  const classes = useStyles({ isRootScope });
132
134
 
133
135
  const expandIcon = <Icon id="dropdown-chevron-down" />;
@@ -135,7 +137,8 @@ const TreeItem = ({ scope, rootId, onScopeSelect, children }) => {
135
137
 
136
138
  const onLabelClickHandler = event => {
137
139
  event.preventDefault();
138
- if (isVirtualScope === false) {
140
+
141
+ if (isSelectable) {
139
142
  onScopeSelect(event, scope.id);
140
143
  }
141
144
  };
@@ -157,9 +160,9 @@ const TreeItem = ({ scope, rootId, onScopeSelect, children }) => {
157
160
  onLabelClick={e => onLabelClickHandler(e)}
158
161
  classes={{
159
162
  group: classes.group,
160
- iconContainer: classNames({ [classes.globalIconContainer]: isRootScope }),
161
- content: classNames({ [classes.virtualScopeContent]: isVirtualScope }),
162
- selected: classNames({ [classes.virtualScopeSelected]: isVirtualScope }),
163
+ iconContainer: classNames({ [classes.rootIconContainer]: isRootScope }),
164
+ content: classNames({ [classes.virtualScopeContent]: !isSelectable }),
165
+ selected: classNames({ [classes.virtualScopeSelected]: !isSelectable }),
163
166
  }}
164
167
  >
165
168
  {children}