codecharta-visualization 1.86.0 → 1.87.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.
Files changed (72) hide show
  1. package/Dockerfile +1 -1
  2. package/app/app.module.ts +6 -2
  3. package/app/codeCharta/codeCharta.model.ts +1 -1
  4. package/app/codeCharta/state/store/dynamicSettings/dynamicSettings.splitter.ts +2 -2
  5. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/currentFocused.selector.ts +8 -0
  6. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.actions.ts +27 -7
  7. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.reducer.spec.ts +27 -11
  8. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.reducer.ts +11 -5
  9. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.selector.ts +4 -0
  10. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.service.spec.ts +2 -2
  11. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.service.ts +1 -1
  12. package/app/codeCharta/state/store/fileSettings/markedPackages/markedPackages.actions.ts +9 -10
  13. package/app/codeCharta/state/store/fileSettings/markedPackages/markedPackages.reducer.spec.ts +37 -7
  14. package/app/codeCharta/state/store/fileSettings/markedPackages/markedPackages.reducer.ts +7 -3
  15. package/app/codeCharta/state/store/fileSettings/markedPackages/util/findIndexOfMarkedPackageOrParent.spec.ts +39 -0
  16. package/app/codeCharta/state/store/fileSettings/markedPackages/util/findIndexOfMarkedPackageOrParent.ts +19 -0
  17. package/app/codeCharta/state/store/state.splitter.spec.ts +4 -4
  18. package/app/codeCharta/state/store.service.spec.ts +3 -3
  19. package/app/codeCharta/state/store.service.ts +5 -0
  20. package/app/codeCharta/ui/artificialIntelligence/__snapshots__/artificialIntelligence.component.spec.ts.snap +13 -16
  21. package/app/codeCharta/ui/artificialIntelligence/artificialIntelligence.component.html +4 -4
  22. package/app/codeCharta/ui/artificialIntelligence/artificialIntelligence.component.scss +0 -4
  23. package/app/codeCharta/ui/artificialIntelligence/artificialIntelligence.component.spec.ts +82 -52
  24. package/app/codeCharta/ui/artificialIntelligence/artificialIntelligence.component.ts +41 -101
  25. package/app/codeCharta/ui/codeMap/codeMap.actions.service.ts +1 -40
  26. package/app/codeCharta/ui/codeMap/codeMap.mouseEvent.service.ts +10 -16
  27. package/app/codeCharta/ui/colorPicker/colorPicker.component.html +16 -7
  28. package/app/codeCharta/ui/colorPicker/colorPicker.component.spec.ts +55 -16
  29. package/app/codeCharta/ui/colorPicker/colorPicker.component.ts +32 -1
  30. package/app/codeCharta/ui/colorPicker/colorPicker.module.ts +3 -1
  31. package/app/codeCharta/ui/customConfigs/customConfigs.component.html +21 -3
  32. package/app/codeCharta/ui/customConfigs/customConfigs.component.scss +1 -0
  33. package/app/codeCharta/ui/customConfigs/customConfigs.component.spec.ts +123 -2
  34. package/app/codeCharta/ui/customConfigs/customConfigs.component.ts +56 -2
  35. package/app/codeCharta/ui/labelledColorPicker/labelledColorPicker.component.html +21 -13
  36. package/app/codeCharta/ui/labelledColorPicker/labelledColorPicker.component.spec.ts +11 -24
  37. package/app/codeCharta/ui/labelledColorPicker/labelledColorPicker.component.ts +1 -40
  38. package/app/codeCharta/ui/labelledColorPicker/labelledColorPicker.module.ts +1 -2
  39. package/app/codeCharta/ui/legendPanel/legendMarkedPackages/legendMarkedPackages.component.ts +2 -2
  40. package/app/codeCharta/ui/nodeContextMenu/focusButtons/focusButtons.component.html +24 -0
  41. package/app/codeCharta/ui/nodeContextMenu/focusButtons/focusButtons.component.spec.ts +55 -0
  42. package/app/codeCharta/ui/nodeContextMenu/focusButtons/focusButtons.component.ts +38 -0
  43. package/app/codeCharta/ui/nodeContextMenu/focusButtons/focusButtons.module.ts +13 -0
  44. package/app/codeCharta/ui/nodeContextMenu/focusButtons/isNodeFocused.pipe.spec.ts +31 -0
  45. package/app/codeCharta/ui/nodeContextMenu/focusButtons/isNodeFocused.pipe.ts +25 -0
  46. package/app/codeCharta/ui/nodeContextMenu/markFolderColorPicker/markFolderColorPicker.component.html +8 -4
  47. package/app/codeCharta/ui/nodeContextMenu/markFolderColorPicker/markFolderColorPicker.component.ts +1 -21
  48. package/app/codeCharta/ui/nodeContextMenu/markFolderColorPicker/markFolderColorPicker.module.ts +1 -2
  49. package/app/codeCharta/ui/nodeContextMenu/nodeContextMenu.component.html +2 -12
  50. package/app/codeCharta/ui/nodeContextMenu/nodeContextMenu.component.scss +3 -1
  51. package/app/codeCharta/ui/nodeContextMenu/nodeContextMenu.component.spec.ts +28 -125
  52. package/app/codeCharta/ui/nodeContextMenu/nodeContextMenu.component.ts +29 -55
  53. package/app/codeCharta/ui/nodeContextMenu/nodeContextMenu.module.ts +2 -0
  54. package/app/codeCharta/ui/ui.ts +0 -2
  55. package/app/codeCharta/util/algorithm/streetLayout/strip/strip.ts +2 -2
  56. package/app/codeCharta/util/algorithm/streetLayout/treeMap.ts +1 -1
  57. package/app/codeCharta/util/algorithm/treeMapLayout/treeMapGenerator.spec.ts +1 -1
  58. package/app/codeCharta/util/algorithm/treeMapLayout/treeMapHelper.spec.ts +2 -2
  59. package/app/codeCharta/util/algorithm/treeMapLayout/treeMapHelper.ts +1 -1
  60. package/app/codeCharta/util/customConfigBuilder.ts +1 -1
  61. package/app/codeCharta/util/dataMocks.ts +2 -2
  62. package/app/codeCharta/util/fileExtensionCalculator.ts +1 -1
  63. package/dist/webpack/bundle.js +963 -934
  64. package/dist/webpack/bundle.js.map +1 -1
  65. package/package.json +6 -5
  66. package/app/codeCharta/state/store/dynamicSettings/focusedNodePath/focusedNodePath.splitter.ts +0 -8
  67. package/app/codeCharta/ui/codeMap/codeMap.actions.service.spec.ts +0 -147
  68. package/app/codeCharta/ui/unfocusButton/unfocusButton.component.html +0 -15
  69. package/app/codeCharta/ui/unfocusButton/unfocusButton.component.scss +0 -5
  70. package/app/codeCharta/ui/unfocusButton/unfocusButton.component.spec.ts +0 -106
  71. package/app/codeCharta/ui/unfocusButton/unfocusButton.component.ts +0 -62
  72. package/app/codeCharta/ui/unfocusButton/unfocusButton.module.ts +0 -7
@@ -1,32 +1,71 @@
1
- import { EventEmitter } from "@angular/core"
1
+ import { Component } from "@angular/core"
2
2
  import { TestBed } from "@angular/core/testing"
3
- import { render, screen } from "@testing-library/angular"
3
+ import { fireEvent, render, screen, waitFor } from "@testing-library/angular"
4
4
 
5
- import { ColorPickerComponent } from "./colorPicker.component"
6
5
  import { ColorPickerModule } from "./colorPicker.module"
7
6
 
8
7
  describe("colorPicker", () => {
9
- const handleColorChange = jest.fn()
8
+ @Component({
9
+ selector: "test-color-picker",
10
+ template: `
11
+ <cc-color-picker
12
+ [hexColor]="hexColor"
13
+ (onColorChange)="hexColor = $event"
14
+ [triggerTemplate]="colorPickerTriggerTemplate"
15
+ ></cc-color-picker>
16
+ <ng-template #colorPickerTriggerTemplate let-isOpen="isOpen" let-isHovered="isHovered">
17
+ <div>isOpen: {{ isOpen }}</div>
18
+ <div>isHovered: {{ isHovered }}</div>
19
+ <div>color: {{ hexColor }}</div>
20
+ </ng-template>
21
+ `
22
+ })
23
+ class TestColorPicker {
24
+ hexColor = "#ffffff"
25
+ }
10
26
 
11
27
  beforeEach(() => {
12
- handleColorChange.mockReset()
13
28
  TestBed.configureTestingModule({
14
29
  imports: [ColorPickerModule]
15
30
  })
16
31
  })
17
32
 
18
- it("should let a user change a color", async () => {
19
- await render(ColorPickerComponent, {
20
- excludeComponentDeclaration: true,
21
- componentProperties: {
22
- hexColor: "#000000",
23
- onColorChange: { emit: handleColorChange } as unknown as EventEmitter<string>
24
- }
25
- })
33
+ it("should let a user change a color within a menu", async () => {
34
+ const { container } = await render(TestColorPicker)
35
+
36
+ expectColorPickerInDom(false)
37
+ expect(screen.queryByText("isOpen: false")).toBeTruthy()
38
+ expect(screen.queryByText("color: #ffffff")).toBeTruthy()
39
+
40
+ fireEvent.click(container.querySelector("cc-color-picker"))
41
+ expectColorPickerInDom(true)
42
+ expect(screen.queryByText("isOpen: true")).toBeTruthy()
26
43
 
27
- const colorPicker = screen.getByRole("colorpicker")
28
44
  // @ts-ignore
29
- ng.probe(colorPicker).componentInstance.onChangeComplete.emit({ $event: {}, color: { hex: "#ffffff" } })
30
- expect(handleColorChange).toHaveBeenCalledWith("#ffffff")
45
+ ng.probe(screen.queryByRole("colorpicker")).componentInstance.onChangeComplete.emit({ $event: {}, color: { hex: "#000000" } })
46
+ expect(await screen.findByText("color: #000000")).toBeTruthy()
47
+
48
+ fireEvent.click(document)
49
+ await waitFor(() => expectColorPickerInDom(false))
50
+ expect(screen.queryByText("isOpen: false")).toBeTruthy()
31
51
  })
52
+
53
+ it("should pass `isHovered` into template", async () => {
54
+ const { container } = await render(TestColorPicker)
55
+ const colorPicker = container.querySelector("cc-color-picker")
56
+
57
+ expect(screen.queryByText("isHovered: false")).toBeTruthy()
58
+
59
+ fireEvent.mouseEnter(colorPicker)
60
+ expect(screen.queryByText("isHovered: true")).toBeTruthy()
61
+
62
+ fireEvent.mouseLeave(colorPicker)
63
+ expect(screen.queryByText("isHovered: false")).toBeTruthy()
64
+ })
65
+
66
+ function expectColorPickerInDom(isOpen: boolean) {
67
+ const colorPickerElement = screen.queryByRole("colorpicker")
68
+ if (isOpen) expect(colorPickerElement).not.toBe(null)
69
+ else expect(colorPickerElement).toBe(null)
70
+ }
32
71
  })
@@ -1,4 +1,5 @@
1
- import { Component, EventEmitter, Input, Output } from "@angular/core"
1
+ import { Component, EventEmitter, HostListener, Input, Output, TemplateRef, ViewChild } from "@angular/core"
2
+ import { MatMenuTrigger, MenuPositionX } from "@angular/material/menu"
2
3
 
3
4
  @Component({
4
5
  selector: "cc-color-picker",
@@ -6,10 +7,40 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"
6
7
  })
7
8
  export class ColorPickerComponent {
8
9
  @Input() hexColor: string
10
+ @Input() openXPosition: MenuPositionX = "after"
11
+ @Input() triggerTemplate: TemplateRef<unknown>
9
12
 
10
13
  @Output() onColorChange = new EventEmitter<string>()
11
14
 
15
+ @ViewChild("colorPickerMenuTrigger") colorPickerMenuTrigger: MatMenuTrigger
16
+
17
+ isHovered = false
18
+
19
+ private isClickInside = false
20
+
12
21
  handleChangeComplete(hexColor: string) {
13
22
  this.onColorChange.emit(hexColor)
14
23
  }
24
+
25
+ @HostListener("mouseenter") onMouseEnter() {
26
+ this.isHovered = true
27
+ }
28
+
29
+ @HostListener("mouseleave") onMouseLeave() {
30
+ this.isHovered = false
31
+ }
32
+
33
+ @HostListener("click") onClick() {
34
+ this.isClickInside = true
35
+ this.colorPickerMenuTrigger.openMenu()
36
+ }
37
+
38
+ @HostListener("document:click")
39
+ handleDocumentClick() {
40
+ if (!this.isClickInside && this.colorPickerMenuTrigger.menuOpen) {
41
+ this.colorPickerMenuTrigger.closeMenu()
42
+ }
43
+
44
+ this.isClickInside = false
45
+ }
15
46
  }
@@ -1,10 +1,12 @@
1
1
  import { CommonModule } from "@angular/common"
2
2
  import { NgModule } from "@angular/core"
3
3
  import { ColorChromeModule } from "ngx-color/chrome"
4
+
5
+ import { MaterialModule } from "../../../material/material.module"
4
6
  import { ColorPickerComponent } from "./colorPicker.component"
5
7
 
6
8
  @NgModule({
7
- imports: [CommonModule, ColorChromeModule],
9
+ imports: [CommonModule, ColorChromeModule, MaterialModule],
8
10
  declarations: [ColorPickerComponent],
9
11
  exports: [ColorPickerComponent]
10
12
  })
@@ -34,8 +34,17 @@
34
34
  <md-button disabled>It is time to add your first CustomConfig.</md-button>
35
35
  </md-menu-item>
36
36
 
37
- <md-menu-item class="custom-configs-item" ng-repeat="customConfigItemGroup in $ctrl._viewModel.dropDownCustomConfigItemGroups">
38
- <md-button class="button-hovering" ng-click="collapsed=!collapsed" md-prevent-menu-close="md-prevent-menu-close">
37
+ <md-menu-item
38
+ class="custom-configs-item"
39
+ ng-repeat="customConfigItemGroup in $ctrl._viewModel.dropDownCustomConfigItemGroups |
40
+ limitTo: $ctrl._viewModel.visibleEntries"
41
+ >
42
+ <md-button
43
+ class="button-hovering"
44
+ ng-click="collapsed=!collapsed"
45
+ ng-style="!customConfigItemGroup.hasApplicableItems && { color: 'rgba(0,0,0,0.3)' }"
46
+ md-prevent-menu-close="md-prevent-menu-close"
47
+ >
39
48
  <span class="collapse-trigger">
40
49
  Custom Configs in <strong>{{ customConfigItemGroup.mapSelectionMode | lowercase }}</strong> mode for
41
50
  <strong>{{ customConfigItemGroup.mapNames }}</strong>
@@ -64,7 +73,8 @@
64
73
  </md-button>
65
74
 
66
75
  <md-button
67
- ng-click="$ctrl.removeCustomConfig(customConfig.id)"
76
+ ng-click="$ctrl.removeCustomConfig(customConfig.id, $index, $parent.$index)"
77
+ md-prevent-menu-close="md-prevent-menu-close"
68
78
  class="action-button remove button-hovering"
69
79
  title="Remove Custom Config"
70
80
  >
@@ -73,6 +83,14 @@
73
83
  </div>
74
84
  </div>
75
85
  </md-menu-item>
86
+ <button
87
+ ng-show="$ctrl._viewModel.showNonApplicableButton"
88
+ ng-click="$ctrl.toggleNonApplicableItems();"
89
+ md-prevent-menu-close="md-prevent-menu-close"
90
+ >
91
+ {{ $ctrl.isExpanded() ? "Hide non-applicable CustomConfigs" : "Show non-applicable CustomConfigs" }}
92
+ <i ng-class="$ctrl.isExpanded() ? 'fa fa-angle-up' : 'fa fa-angle-down'"></i>
93
+ </button>
76
94
  </md-menu-content>
77
95
  </md-menu>
78
96
 
@@ -69,6 +69,7 @@
69
69
  font-size: 10px;
70
70
  line-height: 48px;
71
71
  vertical-align: top;
72
+ color: black;
72
73
  }
73
74
 
74
75
  .action-button {
@@ -13,6 +13,7 @@ import { CustomConfigHelper } from "../../util/customConfigHelper"
13
13
  import { setState } from "../../state/store/state.actions"
14
14
  import { CustomConfig, ExportCustomConfig } from "../../model/customConfig/customConfig.api.model"
15
15
  import { ThreeCameraService } from "../codeMap/threeViewer/threeCameraService"
16
+ import { klona } from "klona"
16
17
 
17
18
  describe("CustomConfigsController", () => {
18
19
  let customConfigsController: CustomConfigsController
@@ -133,12 +134,60 @@ describe("CustomConfigsController", () => {
133
134
 
134
135
  describe("removeCustomConfig", () => {
135
136
  it("should call deleteCustomConfig", () => {
137
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(klona(CUSTOM_CONFIG_ITEM_GROUPS))
138
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
139
+ customConfigsController.initView()
140
+ CustomConfigHelper.deleteCustomConfig = jest.fn()
141
+ const configIdToRemove = "SINGLEfileASampleMap View #1"
142
+
143
+ customConfigsController.removeCustomConfig(configIdToRemove, 0, 0)
144
+
145
+ expect(CustomConfigHelper.deleteCustomConfig).toHaveBeenCalledWith(configIdToRemove)
146
+ })
147
+
148
+ it("should delete element from customConfigItemGroup in dropDownCustomConfigItemGroups when removing a custom config", () => {
149
+ const testDropDownConfigItemGroup = [klona(CUSTOM_CONFIG_ITEM_GROUPS.get("fileAfileBSINGLE"))]
150
+ const configIdToRemove = "SINGLEfileASampleMap View #1"
151
+
152
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(testDropDownConfigItemGroup)
153
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
154
+ customConfigsController.initView()
155
+ CustomConfigHelper.deleteCustomConfig = jest.fn()
156
+
157
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(1)
158
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups[0].customConfigItems).toHaveLength(2)
159
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
160
+
161
+ customConfigsController.removeCustomConfig(configIdToRemove, 0, 0)
162
+
163
+ expect(CustomConfigHelper.deleteCustomConfig).toHaveBeenCalledWith(configIdToRemove)
164
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(1)
165
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups[0].customConfigItems).toHaveLength(1)
166
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
167
+ })
168
+
169
+ it("should delete element from dropDownCustomConfigItemGroups when removing a custom config", () => {
170
+ const testDropDownConfigItemGroup = [klona(CUSTOM_CONFIG_ITEM_GROUPS.get("fileAfileBMultiple"))]
171
+ testDropDownConfigItemGroup[0].customConfigItems.pop()
172
+ const configIdToRemove = "MULTIPLEfileESampleMap View #1"
173
+
174
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue([testDropDownConfigItemGroup])
175
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(testDropDownConfigItemGroup)
176
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
177
+ customConfigsController.initView()
136
178
  CustomConfigHelper.deleteCustomConfig = jest.fn()
137
179
 
138
- const configIdToRemove = 1
139
- customConfigsController.removeCustomConfig(configIdToRemove)
180
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(1)
181
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups[0].customConfigItems).toHaveLength(1)
182
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(1)
183
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeFalsy()
184
+
185
+ customConfigsController.removeCustomConfig(configIdToRemove, 0, 0)
140
186
 
141
187
  expect(CustomConfigHelper.deleteCustomConfig).toHaveBeenCalledWith(configIdToRemove)
188
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(0)
189
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(0)
190
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeFalsy()
142
191
  })
143
192
  })
144
193
 
@@ -214,4 +263,76 @@ describe("CustomConfigsController", () => {
214
263
  expect(customConfigsController["_viewModel"].hasDownloadableConfigs).toBe(true)
215
264
  })
216
265
  })
266
+
267
+ describe("collapse and expand non applicable custom configs", () => {
268
+ it("should not show configs that are not applicable by default", () => {
269
+ const testConfigItemGroups = [[...CUSTOM_CONFIG_ITEM_GROUPS][0], [...CUSTOM_CONFIG_ITEM_GROUPS][2]]
270
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(testConfigItemGroups)
271
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
272
+
273
+ customConfigsController.initView()
274
+
275
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(2)
276
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(0)
277
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
278
+ })
279
+
280
+ it("should show all configs that are applicable by default", () => {
281
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(CUSTOM_CONFIG_ITEM_GROUPS)
282
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
283
+
284
+ customConfigsController.initView()
285
+
286
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(3)
287
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(1)
288
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
289
+ })
290
+
291
+ it("should hide expand button when only applicable custom configs are loaded", () => {
292
+ const testConfigItemGroup = [CUSTOM_CONFIG_ITEM_GROUPS.get("fileAfileBMultiple")]
293
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(testConfigItemGroup)
294
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
295
+
296
+ customConfigsController.initView()
297
+
298
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(1)
299
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(1)
300
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeFalsy()
301
+ })
302
+
303
+ it("should show all custom configs when 'show non-applicable CustomConfigs' button is clicked", () => {
304
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(CUSTOM_CONFIG_ITEM_GROUPS)
305
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
306
+
307
+ customConfigsController.initView()
308
+
309
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(3)
310
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(1)
311
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
312
+
313
+ customConfigsController.toggleNonApplicableItems()
314
+
315
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(3)
316
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(3)
317
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
318
+ })
319
+
320
+ it("should hide all configs that are not applicable when 'show non-applicable CustomConfigs' button is clicked", () => {
321
+ CustomConfigHelper.getCustomConfigItemGroups = jest.fn().mockReturnValue(CUSTOM_CONFIG_ITEM_GROUPS)
322
+ CustomConfigHelper.getCustomConfigs = jest.fn().mockReturnValue(new Map())
323
+
324
+ customConfigsController.initView()
325
+
326
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(3)
327
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(1)
328
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
329
+
330
+ customConfigsController.toggleNonApplicableItems()
331
+ customConfigsController.toggleNonApplicableItems()
332
+
333
+ expect(customConfigsController["_viewModel"].dropDownCustomConfigItemGroups).toHaveLength(3)
334
+ expect(customConfigsController["_viewModel"].visibleEntries).toBe(1)
335
+ expect(customConfigsController["_viewModel"].showNonApplicableButton).toBeTruthy()
336
+ })
337
+ })
217
338
  })
@@ -29,13 +29,19 @@ export class CustomConfigsController implements FilesSelectionSubscriber {
29
29
  private _viewModel: {
30
30
  dropDownCustomConfigItemGroups: CustomConfigItemGroup[]
31
31
  hasDownloadableConfigs: boolean
32
+ showNonApplicableButton: boolean
33
+ visibleEntries: number
32
34
  } = {
33
35
  dropDownCustomConfigItemGroups: [],
34
- hasDownloadableConfigs: false
36
+ hasDownloadableConfigs: false,
37
+ showNonApplicableButton: false,
38
+ visibleEntries: 0
35
39
  }
36
40
 
37
41
  private customConfigFileStateConnector: CustomConfigFileStateConnector
38
42
  private downloadableConfigs: Map<string, ExportCustomConfig> = new Map()
43
+ private previousEntries
44
+ private applicableEntries = 0
39
45
 
40
46
  constructor(
41
47
  private $rootScope: IRootScopeService,
@@ -57,6 +63,7 @@ export class CustomConfigsController implements FilesSelectionSubscriber {
57
63
  initView() {
58
64
  this.loadCustomConfigs()
59
65
  this.preloadDownloadableConfigs()
66
+ this.initVisibleEntries()
60
67
  }
61
68
 
62
69
  loadCustomConfigs() {
@@ -74,8 +81,41 @@ export class CustomConfigsController implements FilesSelectionSubscriber {
74
81
  CustomConfigHelper.applyCustomConfig(configId, this.storeService, this.threeCameraService, this.threeOrbitControlsService)
75
82
  }
76
83
 
77
- removeCustomConfig(configId) {
84
+ removeCustomConfig(configId, customConfigItemIndex, dropDownCustomConfigItemGroupIndex) {
85
+ const { dropDownCustomConfigItemGroups } = this._viewModel
86
+
78
87
  CustomConfigHelper.deleteCustomConfig(configId)
88
+ dropDownCustomConfigItemGroups[dropDownCustomConfigItemGroupIndex].customConfigItems.splice(customConfigItemIndex, 1)
89
+
90
+ if (dropDownCustomConfigItemGroups[dropDownCustomConfigItemGroupIndex].customConfigItems.length === 0) {
91
+ if (dropDownCustomConfigItemGroups[dropDownCustomConfigItemGroupIndex].hasApplicableItems) {
92
+ this.applicableEntries--
93
+ }
94
+
95
+ dropDownCustomConfigItemGroups.splice(dropDownCustomConfigItemGroupIndex, 1)
96
+ this._viewModel.visibleEntries--
97
+ this.previousEntries--
98
+ this.updateButtonVisibility()
99
+ }
100
+ }
101
+
102
+ private initVisibleEntries() {
103
+ this.applicableEntries = 0
104
+ while (this._viewModel.dropDownCustomConfigItemGroups[this.applicableEntries]?.hasApplicableItems) {
105
+ this.applicableEntries++
106
+ }
107
+
108
+ this._viewModel.visibleEntries = this.applicableEntries
109
+ this.updateButtonVisibility()
110
+ }
111
+
112
+ toggleNonApplicableItems() {
113
+ if (!this.isExpanded()) {
114
+ this.previousEntries = this._viewModel.visibleEntries
115
+ this._viewModel.visibleEntries = this._viewModel.dropDownCustomConfigItemGroups.length
116
+ } else {
117
+ this._viewModel.visibleEntries = this.previousEntries
118
+ }
79
119
  }
80
120
 
81
121
  downloadPreloadedCustomConfigs() {
@@ -109,6 +149,20 @@ export class CustomConfigsController implements FilesSelectionSubscriber {
109
149
  }
110
150
  return false
111
151
  }
152
+
153
+ private isExpanded(): boolean {
154
+ return this._viewModel.visibleEntries === this._viewModel.dropDownCustomConfigItemGroups.length
155
+ }
156
+
157
+ private updateButtonVisibility() {
158
+ if (this._viewModel.dropDownCustomConfigItemGroups.length === 0) {
159
+ this._viewModel.showNonApplicableButton = false
160
+ } else if (this.applicableEntries < this._viewModel.dropDownCustomConfigItemGroups.length) {
161
+ this._viewModel.showNonApplicableButton = true
162
+ } else {
163
+ this._viewModel.showNonApplicableButton = false
164
+ }
165
+ }
112
166
  }
113
167
 
114
168
  export const customConfigsComponent = {
@@ -1,15 +1,23 @@
1
- <span [matMenuTriggerFor]="menu" #colorPickerMenuTrigger="matMenuTrigger"></span>
1
+ <cc-color-picker
2
+ [hexColor]="hexColor"
3
+ (onColorChange)="handleColorChange($event)"
4
+ [openXPosition]="'before'"
5
+ [triggerTemplate]="colorPickerTriggerTemplate"
6
+ ></cc-color-picker>
2
7
 
3
- <div class="cc-labelled-color-picker-container">
4
- <div class="cc-color-swatch-container">
5
- <span class="cc-color-swatch" [style.background-color]="hexColor"></span>
6
- <span #brush [style.color]="hexColor | readableColorForBackground" class="cc-color-brush fa fa-paint-brush"></span>
8
+ <ng-template #colorPickerTriggerTemplate let-isOpen="isOpen" let-isHovered="isHovered">
9
+ <div class="cc-labelled-color-picker-container">
10
+ <div class="cc-color-swatch-container">
11
+ <span class="cc-color-swatch" [style.background-color]="hexColor"></span>
12
+ <span
13
+ class="cc-color-brush fa fa-paint-brush"
14
+ [style.color]="hexColor | readableColorForBackground"
15
+ [style.opacity]="isHovered || isOpen ? '1' : '0'"
16
+ >
17
+ </span>
18
+ </div>
19
+ <div>
20
+ <span *ngFor="let label of labels"> {{ label }} <br *ngIf="!last" /></span>
21
+ </div>
7
22
  </div>
8
- <div>
9
- <span *ngFor="let label of labels"> {{ label }} <br *ngIf="!last" /></span>
10
- </div>
11
- </div>
12
-
13
- <mat-menu #menu="matMenu" xPosition="before" [hasBackdrop]="false" class="cc-color-picker-menu">
14
- <cc-color-picker [hexColor]="hexColor" (onColorChange)="handleColorChange($event)"></cc-color-picker>
15
- </mat-menu>
23
+ </ng-template>
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from "@angular/core"
2
2
  import { TestBed } from "@angular/core/testing"
3
- import { render, fireEvent, screen, waitForElementToBeRemoved, waitFor } from "@testing-library/angular"
3
+ import { render, fireEvent, screen, waitForElementToBeRemoved } from "@testing-library/angular"
4
4
 
5
5
  import { LabelledColorPickerComponent } from "./labelledColorPicker.component"
6
6
  import { LabelledColorPickerModule } from "./labelledColorPicker.module"
@@ -15,7 +15,7 @@ describe("labelledColorPicker", () => {
15
15
  })
16
16
  })
17
17
 
18
- it("should let a user change a color within a menu", async () => {
18
+ it("should display label and handle brush visibility", async () => {
19
19
  const { container } = await render(LabelledColorPickerComponent, {
20
20
  excludeComponentDeclaration: true,
21
21
  componentProperties: {
@@ -24,41 +24,28 @@ describe("labelledColorPicker", () => {
24
24
  onColorChange: { emit: handleColorChange } as unknown as EventEmitter<string>
25
25
  }
26
26
  })
27
+ const colorPickerTrigger = container.querySelector("cc-color-picker")
27
28
 
28
29
  expect(container.textContent).toMatch("pretty color")
29
30
 
30
- fireEvent.click(container)
31
- const colorPicker = await waitFor(() => screen.getByRole("colorpicker"))
31
+ fireEvent.mouseEnter(colorPickerTrigger)
32
+ expectBrushVisibility(container, true)
33
+
34
+ fireEvent.click(colorPickerTrigger)
35
+ const colorPicker = screen.getByRole("colorpicker")
32
36
  expectBrushVisibility(container, true)
33
37
 
34
38
  // @ts-ignore
35
39
  ng.probe(colorPicker).componentInstance.onChangeComplete.emit({ $event: {}, color: { hex: "#ffffff" } })
36
40
  expect(handleColorChange).toHaveBeenCalledWith("#ffffff")
37
41
 
38
- fireEvent.click(document)
42
+ fireEvent.mouseLeave(colorPickerTrigger)
43
+ expectBrushVisibility(container, true) // still true as color picker is still open
39
44
 
45
+ fireEvent.click(document)
40
46
  await waitForElementToBeRemoved(screen.getByRole("colorpicker"))
41
47
  expectBrushVisibility(container, false)
42
48
  })
43
-
44
- it("should add/remove brush on mouseenter/mouseleave", async () => {
45
- const { container } = await render(LabelledColorPickerComponent, {
46
- excludeComponentDeclaration: true,
47
- componentProperties: {
48
- hexColor: "#000000",
49
- labels: ["pretty color"],
50
- onColorChange: { emit: handleColorChange } as unknown as EventEmitter<string>
51
- }
52
- })
53
-
54
- expectBrushVisibility(container, false)
55
-
56
- fireEvent.mouseEnter(container)
57
- expectBrushVisibility(container, true)
58
-
59
- fireEvent.mouseLeave(container)
60
- expectBrushVisibility(container, false)
61
- })
62
49
  })
63
50
 
64
51
  function expectBrushVisibility(container: Element, isVisible: boolean) {
@@ -1,6 +1,5 @@
1
1
  import "./labelledColorPicker.component.scss"
2
- import { Component, ElementRef, EventEmitter, HostListener, Input, Output, ViewChild } from "@angular/core"
3
- import { MatMenuTrigger } from "@angular/material/menu"
2
+ import { Component, EventEmitter, Input, Output } from "@angular/core"
4
3
 
5
4
  @Component({
6
5
  selector: "cc-labelled-color-picker",
@@ -12,44 +11,6 @@ export class LabelledColorPickerComponent {
12
11
 
13
12
  @Output() onColorChange = new EventEmitter<string>()
14
13
 
15
- @ViewChild("colorPickerMenuTrigger") colorPickerMenuTrigger: MatMenuTrigger
16
- @ViewChild("brush") brush: ElementRef
17
-
18
- private isClickInside = false
19
-
20
- @HostListener("mouseenter") onMouseEnter() {
21
- this.brush.nativeElement.style.opacity = "1"
22
- }
23
-
24
- @HostListener("mouseleave") onMouseLeave() {
25
- if (!this.colorPickerMenuTrigger.menuOpen) this.brush.nativeElement.style.opacity = "0"
26
- }
27
-
28
- @HostListener("click")
29
- onClick() {
30
- this.isClickInside = true
31
-
32
- const menuOpenedSubscription = this.colorPickerMenuTrigger.menuOpened.subscribe(() => {
33
- this.brush.nativeElement.style.opacity = "1"
34
- menuOpenedSubscription.unsubscribe()
35
- })
36
- const menuClosedSubscription = this.colorPickerMenuTrigger.menuClosed.subscribe(() => {
37
- this.brush.nativeElement.style.opacity = "0"
38
- menuClosedSubscription.unsubscribe()
39
- })
40
-
41
- this.colorPickerMenuTrigger.openMenu()
42
- }
43
-
44
- @HostListener("document:click")
45
- handleDocumentClick() {
46
- if (!this.isClickInside && this.colorPickerMenuTrigger && this.colorPickerMenuTrigger.menuOpen) {
47
- this.colorPickerMenuTrigger.closeMenu()
48
- }
49
-
50
- this.isClickInside = false
51
- }
52
-
53
14
  handleColorChange(hexColor: string) {
54
15
  this.onColorChange.emit(hexColor)
55
16
  }
@@ -1,13 +1,12 @@
1
1
  import { CommonModule } from "@angular/common"
2
2
  import { NgModule } from "@angular/core"
3
3
 
4
- import { MaterialModule } from "../../../material/material.module"
5
4
  import { ColorPickerModule } from "../colorPicker/colorPicker.module"
6
5
  import { LabelledColorPickerComponent } from "./labelledColorPicker.component"
7
6
  import { ReadableColorForBackgroundPipe } from "./readableColorForBackground.pipe"
8
7
 
9
8
  @NgModule({
10
- imports: [CommonModule, ColorPickerModule, MaterialModule],
9
+ imports: [CommonModule, ColorPickerModule],
11
10
  declarations: [LabelledColorPickerComponent, ReadableColorForBackgroundPipe],
12
11
  exports: [LabelledColorPickerComponent]
13
12
  })
@@ -5,7 +5,7 @@ import { KeyValue } from "@angular/common"
5
5
 
6
6
  import { Store } from "../../../state/angular-redux/store"
7
7
  import { legendMarkedPackagesSelector, MarkedPackagesMap } from "./legendMarkedPackages.selector"
8
- import { calculateMarkedPackages } from "../../../state/store/fileSettings/markedPackages/markedPackages.actions"
8
+ import { markPackages } from "../../../state/store/fileSettings/markedPackages/markedPackages.actions"
9
9
 
10
10
  type MarkedPackagesMapKeyValue = KeyValue<keyof MarkedPackagesMap, MarkedPackagesMap[keyof MarkedPackagesMap]>
11
11
 
@@ -24,7 +24,7 @@ export class LegendMarkedPackagesComponent {
24
24
 
25
25
  handleColorChange(newHexColor: string, paths: string[]) {
26
26
  this.store.dispatch(
27
- calculateMarkedPackages(
27
+ markPackages(
28
28
  paths.map(path => ({
29
29
  color: newHexColor,
30
30
  path
@@ -0,0 +1,24 @@
1
+ <ng-container *ngIf="currentFocusedNodePath$ | async | isNodeFocused: codeMapNode; let isNodeFocused">
2
+ <ng-container *ngIf="!isNodeFocused.isNodeFocused">
3
+ <button mat-button (click)="handleFocusNodeClicked()" id="focus-button" title="Only show selected nodes with children-nodes">
4
+ <i class="fa fa-bullseye"></i> FOCUS
5
+ </button>
6
+ </ng-container>
7
+
8
+ <ng-container *ngIf="isNodeFocused.isNodeFocused || isNodeFocused.isParentFocused">
9
+ <button mat-button aria-label="Remove Focus from node" (click)="handleUnfocusNodeClicked()" title="Show previous focused node">
10
+ <i class="fa fa-bullseye"></i>
11
+ {{ isNodeFocused.isParentFocused ? "UNFOCUS PARENT" : "UNFOCUS" }}
12
+ </button>
13
+ <button
14
+ mat-button
15
+ *ngIf="hasPreviousFocusedNodePath$ | async"
16
+ aria-label="Remove Focus from all nodes"
17
+ (click)="handleUnfocusAllNodesClicked()"
18
+ title="Show complete map"
19
+ >
20
+ <i class="fa fa-bullseye"></i>
21
+ UNFOCUS ALL
22
+ </button>
23
+ </ng-container>
24
+ </ng-container>