@openmfp/webcomponents 0.6.1 → 0.6.8

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 (142) hide show
  1. package/mfp-wc-dashboard.js +153 -0
  2. package/mfp-webcomponents.js +573 -0
  3. package/package.json +14 -68
  4. package/.github/workflows/pipeline.yaml +0 -41
  5. package/.storybook/main.ts +0 -34
  6. package/.storybook/preview.ts +0 -29
  7. package/.storybook/tsconfig.json +0 -11
  8. package/AGENTS.md +0 -153
  9. package/CODEOWNERS +0 -6
  10. package/CONTRIBUTING.md +0 -95
  11. package/LICENSE +0 -201
  12. package/LICENSES/Apache-2.0.txt +0 -73
  13. package/README.md +0 -91
  14. package/REUSE.toml +0 -9
  15. package/angular.json +0 -157
  16. package/docs/dashboard.md +0 -358
  17. package/docs/declarative-form.md +0 -178
  18. package/docs/declarative-table-card.md +0 -235
  19. package/docs/declarative-table.md +0 -315
  20. package/eslint.config.js +0 -41
  21. package/projects/ngx/cards/favorites/favorites.component.html +0 -12
  22. package/projects/ngx/cards/favorites/favorites.component.scss +0 -50
  23. package/projects/ngx/cards/favorites/favorites.component.ts +0 -19
  24. package/projects/ngx/cards/public-api.ts +0 -4
  25. package/projects/ngx/cards/service-status/service-status-card.component.html +0 -15
  26. package/projects/ngx/cards/service-status/service-status-card.component.scss +0 -87
  27. package/projects/ngx/cards/service-status/service-status-card.component.ts +0 -36
  28. package/projects/ngx/cards/stories/visited-service-card.stories.ts +0 -149
  29. package/projects/ngx/cards/visited-service-card/visited-service-card.component.html +0 -17
  30. package/projects/ngx/cards/visited-service-card/visited-service-card.component.scss +0 -34
  31. package/projects/ngx/cards/visited-service-card/visited-service-card.component.ts +0 -22
  32. package/projects/ngx/cards/whats-new/whats-new.component.html +0 -10
  33. package/projects/ngx/cards/whats-new/whats-new.component.scss +0 -25
  34. package/projects/ngx/cards/whats-new/whats-new.component.ts +0 -46
  35. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.html +0 -28
  36. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.scss +0 -44
  37. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.spec.ts +0 -85
  38. package/projects/ngx/declarative-ui/dashboard/add-card-dialog/add-card-dialog.component.ts +0 -58
  39. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.html +0 -29
  40. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.scss +0 -63
  41. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.spec.ts +0 -255
  42. package/projects/ngx/declarative-ui/dashboard/card/dashboard-card.component.ts +0 -75
  43. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.spec.ts +0 -76
  44. package/projects/ngx/declarative-ui/dashboard/card/utils/dashboard-card-registry.ts +0 -109
  45. package/projects/ngx/declarative-ui/dashboard/card/utils/index.ts +0 -4
  46. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.spec.ts +0 -141
  47. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-angular-card.ts +0 -44
  48. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.spec.ts +0 -142
  49. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-sap-card.ts +0 -52
  50. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.spec.ts +0 -107
  51. package/projects/ngx/declarative-ui/dashboard/card/utils/mount-wc-card.ts +0 -22
  52. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.html +0 -134
  53. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.scss +0 -88
  54. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.spec.ts +0 -354
  55. package/projects/ngx/declarative-ui/dashboard/dashboard/dashboard.component.ts +0 -238
  56. package/projects/ngx/declarative-ui/dashboard/dashboard/index.ts +0 -1
  57. package/projects/ngx/declarative-ui/dashboard/index.ts +0 -5
  58. package/projects/ngx/declarative-ui/dashboard/models/constants.ts +0 -2
  59. package/projects/ngx/declarative-ui/dashboard/models/dashboard.model.ts +0 -50
  60. package/projects/ngx/declarative-ui/dashboard/models/index.ts +0 -1
  61. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.html +0 -28
  62. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.scss +0 -85
  63. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.spec.ts +0 -104
  64. package/projects/ngx/declarative-ui/dashboard/section/dashboard-section.component.ts +0 -23
  65. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.html +0 -62
  66. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.scss +0 -12
  67. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.spec.ts +0 -301
  68. package/projects/ngx/declarative-ui/form/declarative-form/declarative-form.component.ts +0 -166
  69. package/projects/ngx/declarative-ui/form/declarative-form/index.ts +0 -1
  70. package/projects/ngx/declarative-ui/form/index.ts +0 -2
  71. package/projects/ngx/declarative-ui/form/models/form-field-definition.ts +0 -15
  72. package/projects/ngx/declarative-ui/form/models/index.ts +0 -1
  73. package/projects/ngx/declarative-ui/form/utils/set-property-by-path.ts +0 -30
  74. package/projects/ngx/declarative-ui/models/index.ts +0 -2
  75. package/projects/ngx/declarative-ui/models/resource.ts +0 -5
  76. package/projects/ngx/declarative-ui/models/ui-definition.ts +0 -95
  77. package/projects/ngx/declarative-ui/public-api.ts +0 -4
  78. package/projects/ngx/declarative-ui/stories/add-card-dialog.stories.ts +0 -91
  79. package/projects/ngx/declarative-ui/stories/background-lightblue.png +0 -0
  80. package/projects/ngx/declarative-ui/stories/dashboard.cards.ts +0 -107
  81. package/projects/ngx/declarative-ui/stories/dashboard.stories.ts +0 -296
  82. package/projects/ngx/declarative-ui/stories/declarative-form.stories.ts +0 -149
  83. package/projects/ngx/declarative-ui/stories/declarative-table-card.stories.ts +0 -358
  84. package/projects/ngx/declarative-ui/stories/declarative-table.stories.ts +0 -363
  85. package/projects/ngx/declarative-ui/stories/pods-table.config.ts +0 -188
  86. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.html +0 -138
  87. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.scss +0 -21
  88. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.spec.ts +0 -345
  89. package/projects/ngx/declarative-ui/table/declarative-table/declarative-table.component.ts +0 -61
  90. package/projects/ngx/declarative-ui/table/declarative-table/index.ts +0 -1
  91. package/projects/ngx/declarative-ui/table/index.ts +0 -2
  92. package/projects/ngx/declarative-ui/table/models/index.ts +0 -14
  93. package/projects/ngx/declarative-ui/table/models/table.model.ts +0 -17
  94. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.spec.ts +0 -146
  95. package/projects/ngx/declarative-ui/table/utils/cssRules.engine.ts +0 -69
  96. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.spec.ts +0 -70
  97. package/projects/ngx/declarative-ui/table/utils/field-definition.utils.ts +0 -13
  98. package/projects/ngx/declarative-ui/table/utils/proccess-fields.spec.ts +0 -511
  99. package/projects/ngx/declarative-ui/table/utils/proccess-fields.ts +0 -71
  100. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.spec.ts +0 -372
  101. package/projects/ngx/declarative-ui/table/utils/resource-field-by-path.ts +0 -98
  102. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-cell.constants.ts +0 -5
  103. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.html +0 -1
  104. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.scss +0 -0
  105. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.spec.ts +0 -119
  106. package/projects/ngx/declarative-ui/table/value-cell/boolean-value/boolean-value.component.ts +0 -35
  107. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.html +0 -7
  108. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.scss +0 -0
  109. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.spec.ts +0 -114
  110. package/projects/ngx/declarative-ui/table/value-cell/link-value/link-value.component.ts +0 -19
  111. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.html +0 -7
  112. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.scss +0 -10
  113. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.spec.ts +0 -188
  114. package/projects/ngx/declarative-ui/table/value-cell/secret-value/secret-value.component.ts +0 -16
  115. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.html +0 -59
  116. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.scss +0 -33
  117. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.spec.ts +0 -316
  118. package/projects/ngx/declarative-ui/table/value-cell/value-cell.component.ts +0 -115
  119. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.html +0 -156
  120. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.scss +0 -123
  121. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.spec.ts +0 -786
  122. package/projects/ngx/declarative-ui/table-card/declarative-table-card.component.ts +0 -286
  123. package/projects/ngx/declarative-ui/table-card/index.ts +0 -2
  124. package/projects/ngx/declarative-ui/table-card/models/configs.ts +0 -46
  125. package/projects/ngx/declarative-ui/tsconfig.lib.json +0 -11
  126. package/projects/ngx/declarative-ui/tsconfig.lib.prod.json +0 -9
  127. package/projects/ngx/declarative-ui/tsconfig.spec.json +0 -9
  128. package/projects/ngx/ng-package.json +0 -8
  129. package/projects/ngx/package.json +0 -22
  130. package/projects/ngx/public-api.ts +0 -2
  131. package/projects/ngx/tsconfig.lib.json +0 -11
  132. package/projects/ngx/tsconfig.lib.prod.json +0 -9
  133. package/projects/webcomponents/main.ts +0 -92
  134. package/projects/webcomponents/tsconfig.app.json +0 -9
  135. package/projects/webcomponents-dashboard/main.ts +0 -15
  136. package/projects/webcomponents-dashboard/tsconfig.app.json +0 -9
  137. package/renovate.json +0 -6
  138. package/scripts/bundle-wc.mjs +0 -79
  139. package/tsconfig.json +0 -37
  140. package/tsconfig.spec.json +0 -8
  141. package/tsconfig.storybook.json +0 -16
  142. package/vitest.config.ts +0 -26
@@ -1,88 +0,0 @@
1
- @import 'gridstack/dist/gridstack.min.css';
2
-
3
- mfp-dashboard,
4
- mfp-wc-dashboard {
5
- display: block;
6
- margin: 0;
7
- padding: 0;
8
- min-height: 100%;
9
- background: var(--sapShellColor, #354a5e);
10
- background-size: cover;
11
- background-position: center;
12
- background-repeat: no-repeat;
13
- background-attachment: local;
14
- }
15
-
16
- .mfp-dashboard {
17
- padding: 2rem;
18
- }
19
-
20
- .mfp-sections-container {
21
- display: grid;
22
- grid-template-columns: repeat(12, 1fr);
23
- grid-auto-rows: auto;
24
- align-content: start;
25
- gap: 1rem;
26
-
27
- @media (max-width: 726px) {
28
- grid-template-columns: 1fr;
29
- }
30
-
31
- @media (min-width: 727px) and (max-width: 1200px) {
32
- grid-template-columns: repeat(8, 1fr);
33
- }
34
- }
35
-
36
- .mfp-dashboard__topbar {
37
- grid-column: 1 / -1;
38
- align-self: start;
39
- display: flex;
40
- flex-direction: column;
41
- gap: 1rem;
42
- padding-bottom: 3rem;
43
- width: 100%;
44
- min-width: 0;
45
- }
46
-
47
- .mfp-dashboard__topbar-row {
48
- display: flex;
49
- align-items: flex-start;
50
- justify-content: space-between;
51
- gap: 1rem;
52
- }
53
-
54
- .mfp-dashboard__header {
55
- display: flex;
56
- flex-direction: column;
57
- gap: 0.25rem;
58
- }
59
-
60
- .mfp-dashboard__toolbar {
61
- flex-shrink: 0;
62
- display: flex;
63
- gap: 0.5rem;
64
- }
65
-
66
- .mfp-dashboard__subheader {
67
- display: flex;
68
- align-items: center;
69
- gap: 1rem;
70
- }
71
-
72
- .mfp-dashboard__edit-bar {
73
- position: fixed;
74
- bottom: 20px;
75
- left: 16px;
76
- right: 16px;
77
- z-index: 1000;
78
- display: flex;
79
- justify-content: flex-end;
80
- align-items: center;
81
- gap: 8px;
82
- padding: 7px 16px;
83
- background: var(--sapPageFooter_Background, white);
84
- border-radius: var(--sapElement_BorderCornerRadius, 12px);
85
- box-shadow: 0px 0px 0px 1px rgba(34, 53, 72, 0.48), 0px 2px 8px 0px rgba(34, 53, 72, 0.3);
86
- box-sizing: border-box;
87
- overflow: hidden;
88
- }
@@ -1,354 +0,0 @@
1
- import { resetDashboardCardRegistry } from '../card/utils/dashboard-card-registry';
2
- import { CardConfig, SectionConfig } from '../models';
3
- import { Dashboard } from './dashboard.component';
4
- import { ComponentFixture, TestBed } from '@angular/core/testing';
5
-
6
- vi.mock('gridstack', () => ({}));
7
-
8
- vi.mock('gridstack/dist/angular', async () => {
9
- const { Component, EventEmitter, Input, Output } =
10
- await import('@angular/core');
11
-
12
- @Component({
13
- selector: 'mfp-gridstack',
14
- standalone: true,
15
- template: '<ng-content />',
16
- })
17
- class GridstackComponent {
18
- @Input() options?: unknown;
19
- @Output() readonly changeCB = new EventEmitter<unknown>();
20
-
21
- gridstackItems = {
22
- toArray: () => [],
23
- };
24
- }
25
-
26
- @Component({
27
- selector: 'mfp-gridstack-item',
28
- standalone: true,
29
- template: '<ng-content />',
30
- })
31
- class GridstackItemComponent {
32
- @Input() options?: unknown;
33
- }
34
-
35
- return {
36
- GridstackComponent,
37
- GridstackItemComponent,
38
- };
39
- });
40
-
41
- type Fixture = ComponentFixture<Dashboard>;
42
-
43
- function setup(): { fixture: Fixture; component: Dashboard } {
44
- const fixture = TestBed.createComponent(Dashboard);
45
- const component = fixture.componentInstance;
46
- return { fixture, component };
47
- }
48
-
49
- function root(fixture: Fixture): ShadowRoot | HTMLElement {
50
- return fixture.nativeElement.shadowRoot ?? fixture.nativeElement;
51
- }
52
-
53
- describe('Dashboard', () => {
54
- beforeEach(async () => {
55
- resetDashboardCardRegistry();
56
- await TestBed.configureTestingModule({
57
- imports: [Dashboard],
58
- }).compileComponents();
59
- });
60
-
61
- it('creates and applies the configured background image', () => {
62
- const { fixture, component } = setup();
63
-
64
- fixture.componentRef.setInput('config', {
65
- title: 'Operations',
66
- backgroundImageUrl: 'https://example.com/bg.png',
67
- });
68
- fixture.detectChanges();
69
-
70
- expect(component).toBeTruthy();
71
- expect(fixture.nativeElement.style.backgroundImage).toContain(
72
- 'https://example.com/bg.png',
73
- );
74
- });
75
-
76
- it('renders dashboard metadata, sections and loose cards from the provided inputs', () => {
77
- const { fixture, component } = setup();
78
-
79
- fixture.componentRef.setInput('config', {
80
- title: 'Operations',
81
- description: 'Platform status',
82
- });
83
- component.sections.set([{ id: 'alpha', title: 'Alpha' }]);
84
- component.cards.set([
85
- { id: 'card-1', component: 'mfp-a', sectionId: 'alpha' },
86
- { id: 'card-2', component: 'mfp-b' },
87
- ]);
88
- fixture.detectChanges();
89
-
90
- expect(root(fixture).textContent).toContain('Operations');
91
- expect(root(fixture).textContent).toContain('Platform status');
92
- expect(
93
- root(fixture).querySelectorAll('mfp-dashboard-section'),
94
- ).toHaveLength(1);
95
- expect(root(fixture).querySelectorAll('gridstack-item')).toHaveLength(1);
96
- expect(
97
- root(fixture).querySelector('.mfp-dashboard__toolbar ui5-button'),
98
- ).toBeNull();
99
- });
100
-
101
- it('switches the template into edit mode and opens the add-card dialog from the toolbar', () => {
102
- const { fixture, component } = setup();
103
-
104
- fixture.componentRef.setInput('config', {
105
- title: 'Operations',
106
- editable: true,
107
- });
108
- component.cards.set([{ id: 'card-1', component: 'mfp-a' }]);
109
- fixture.detectChanges();
110
-
111
- const editButton = root(fixture).querySelector(
112
- '.mfp-dashboard__toolbar ui5-button',
113
- );
114
-
115
- editButton?.dispatchEvent(new Event('click'));
116
- fixture.detectChanges();
117
-
118
- const addCardButton = root(fixture).querySelector('#add-card-btn');
119
-
120
- expect(component.editMode()).toBe(true);
121
- expect(addCardButton).not.toBeNull();
122
- expect(
123
- root(fixture).querySelectorAll('.mfp-dashboard__edit-bar ui5-button'),
124
- ).toHaveLength(2);
125
-
126
- addCardButton?.dispatchEvent(new Event('click'));
127
- fixture.detectChanges();
128
-
129
- expect(component.cardDialogOpen()).toBe(true);
130
- });
131
-
132
- it('computes added components, section cards, loose cards and drag options from state', () => {
133
- const { component } = setup();
134
- const cards: CardConfig[] = [
135
- { id: 'card-1', component: 'mfp-a', sectionId: 'alpha' },
136
- { id: 'card-2', component: 'mfp-b' },
137
- { id: 'card-3', component: 'mfp-c' },
138
- ];
139
- const gridOptions = (
140
- component as unknown as { gridOptions: () => { disableDrag: boolean } }
141
- ).gridOptions;
142
-
143
- component.cards.set(cards);
144
-
145
- expect(component.addedCardsIds()).toEqual(
146
- new Set(['card-1', 'card-2', 'card-3']),
147
- );
148
- expect(component.sectionCards()('alpha')).toEqual([cards[0]]);
149
- expect(component.looseCards()).toEqual([cards[1], cards[2]]);
150
- expect(gridOptions().disableDrag).toBe(true);
151
-
152
- component.editMode.set(true);
153
-
154
- expect(gridOptions().disableDrag).toBe(false);
155
- });
156
-
157
- it('captures snapshots and grid positions when entering edit mode', () => {
158
- const { component } = setup();
159
- const sections: SectionConfig[] = [{ id: 'alpha', title: 'Alpha' }];
160
- const cards: CardConfig[] = [
161
- { id: 'card-1', component: 'mfp-a', x: 0, y: 0 },
162
- { id: 'card-2', component: 'mfp-b', sectionId: 'alpha', x: 1, y: 1 },
163
- ];
164
-
165
- component.sections.set(sections);
166
- component.cards.set(cards);
167
- (component as unknown as { gridStackItems: () => unknown }).gridStackItems =
168
- () => ({
169
- gridstackItems: {
170
- toArray: () => [
171
- { options: { id: 'card-1', x: 4, y: 2 } },
172
- { options: { id: 'card-2', x: 1, y: 3 } },
173
- ],
174
- },
175
- });
176
-
177
- component.enterEditMode();
178
-
179
- expect(component.editMode()).toBe(true);
180
- expect(
181
- (component as unknown as { sectionsSnapshot: SectionConfig[] })
182
- .sectionsSnapshot,
183
- ).toEqual(sections);
184
- expect(
185
- (component as unknown as { cardsSnapshot: CardConfig[] }).cardsSnapshot,
186
- ).toEqual(cards);
187
- expect(component.cardsPosition.get('card-1')).toEqual({ x: 4, y: 2 });
188
- expect(component.cardsPosition.get('card-2')).toEqual({ x: 1, y: 3 });
189
- });
190
-
191
- it('emits the saved payload and persists the latest order on save', () => {
192
- const { component } = setup();
193
- const sections: SectionConfig[] = [{ id: 'alpha', title: 'Alpha' }];
194
- const cards: CardConfig[] = [{ id: 'card-1', component: 'mfp-a' }];
195
- const emitted: { sections: SectionConfig[]; cards: CardConfig[] }[] = [];
196
-
197
- component.sections.set(sections);
198
- component.cards.set(cards);
199
- component.editMode.set(true);
200
- component.saved.subscribe((value) => emitted.push(value));
201
- component.onOrderChange({
202
- nodes: [{ id: 'card-1', x: 7, y: 5 }],
203
- } as never);
204
-
205
- component.saveEdit();
206
-
207
- expect(emitted).toEqual([
208
- {
209
- sections,
210
- cards: [{ id: 'card-1', component: 'mfp-a', x: 7, y: 5 }],
211
- },
212
- ]);
213
- expect(component.cardsPosition.get('card-1')).toEqual({ x: 7, y: 5 });
214
- expect(component.editMode()).toBe(false);
215
- });
216
-
217
- it('restores snapshot data and saved positions when edit mode is cancelled', () => {
218
- const { component } = setup();
219
- const sections: SectionConfig[] = [{ id: 'alpha', title: 'Alpha' }];
220
- const cards: CardConfig[] = [
221
- { id: 'card-1', component: 'mfp-a', x: 0, y: 0 },
222
- { id: 'card-2', component: 'mfp-b', sectionId: 'alpha', x: 1, y: 1 },
223
- ];
224
-
225
- component.sections.set(sections);
226
- component.cards.set(cards);
227
- (component as unknown as { gridStackItems: () => unknown }).gridStackItems =
228
- () => ({
229
- gridstackItems: {
230
- toArray: () => [
231
- { options: { id: 'card-1', x: 3, y: 6 } },
232
- { options: { id: 'card-2', x: 8, y: 1 } },
233
- ],
234
- },
235
- });
236
- component.enterEditMode();
237
- component.sections.set([{ id: 'beta', title: 'Beta' }]);
238
- component.cards.set([{ id: 'temp', component: 'mfp-temp', x: 9, y: 9 }]);
239
- component.cardDialogOpen.set(true);
240
-
241
- component.cancelEdit();
242
-
243
- expect(component.sections()).toEqual(sections);
244
- expect(component.cards()).toEqual([
245
- { id: 'card-1', component: 'mfp-a', x: 3, y: 6 },
246
- { id: 'card-2', component: 'mfp-b', sectionId: 'alpha', x: 8, y: 1 },
247
- ]);
248
- expect(component.cardDialogOpen()).toBe(false);
249
- expect(component.editMode()).toBe(false);
250
- });
251
-
252
- it('removes sections together with their cards and removes loose cards by id', () => {
253
- const { component } = setup();
254
-
255
- component.sections.set([
256
- { id: 'alpha', title: 'Alpha' },
257
- { id: 'beta', title: 'Beta' },
258
- ]);
259
- component.cards.set([
260
- { id: 'card-1', component: 'mfp-a', sectionId: 'alpha' },
261
- { id: 'card-2', component: 'mfp-b', sectionId: 'beta' },
262
- { id: 'card-3', component: 'mfp-c' },
263
- ]);
264
-
265
- component.removeSection('alpha');
266
- component.removeCard('card-3');
267
-
268
- expect(component.sections()).toEqual([{ id: 'beta', title: 'Beta' }]);
269
- expect(component.cards()).toEqual([
270
- { id: 'card-2', component: 'mfp-b', sectionId: 'beta' },
271
- ]);
272
- });
273
-
274
- it('opens and closes the add-card dialog', () => {
275
- const { component } = setup();
276
-
277
- component.openCardPanel();
278
- expect(component.cardDialogOpen()).toBe(true);
279
-
280
- component.closeCardPanel();
281
- expect(component.cardDialogOpen()).toBe(false);
282
- });
283
-
284
- it('adds new cards and closes the panel', () => {
285
- const { component } = setup();
286
-
287
- component.cards.set([{ id: 'card-1', component: 'mfp-a' }]);
288
- component.cardDialogOpen.set(true);
289
-
290
- component.onCardsAdded([
291
- {
292
- id: 'template-card',
293
- component: 'mfp-b',
294
- label: 'Table',
295
- componentInputs: { size: 'L' },
296
- },
297
- ]);
298
-
299
- expect(component.cards()).toEqual([
300
- { id: 'card-1', component: 'mfp-a' },
301
- {
302
- id: 'template-card',
303
- component: 'mfp-b',
304
- label: 'Table',
305
- componentInputs: { size: 'L' },
306
- },
307
- ]);
308
- expect(component.cardDialogOpen()).toBe(false);
309
- });
310
-
311
- it('preserves card constraint fields (maxH/maxW/minH/minW) through saveEdit', () => {
312
- const { component } = setup();
313
- const cards: CardConfig[] = [
314
- {
315
- id: 'card-1',
316
- component: 'mfp-a',
317
- x: 0,
318
- y: 0,
319
- maxH: 4,
320
- maxW: 6,
321
- minH: 1,
322
- minW: 2,
323
- },
324
- ];
325
-
326
- component.cards.set(cards);
327
- component.editMode.set(true);
328
- component.saved.subscribe(() => false);
329
- component.onOrderChange({
330
- nodes: [{ id: 'card-1', x: 1, y: 2 }],
331
- } as never);
332
-
333
- component.saveEdit();
334
-
335
- expect(component.cards()[0]).toMatchObject({
336
- maxH: 4,
337
- maxW: 6,
338
- minH: 1,
339
- minW: 2,
340
- });
341
- });
342
-
343
- it('still closes the add-card dialog when no cards were selected', () => {
344
- const { component } = setup();
345
-
346
- component.cardDialogOpen.set(true);
347
- component.cards.set([{ id: 'card-1', component: 'mfp-a' }]);
348
-
349
- component.onCardsAdded([]);
350
-
351
- expect(component.cards()).toEqual([{ id: 'card-1', component: 'mfp-a' }]);
352
- expect(component.cardDialogOpen()).toBe(false);
353
- });
354
- });
@@ -1,238 +0,0 @@
1
- import { ButtonSettings } from '../../models/ui-definition';
2
- import { AddCardDialog } from '../add-card-dialog/add-card-dialog.component';
3
- import { addComponentToRegistry } from '../card/utils/dashboard-card-registry';
4
- import { DashboardCard } from '../card/dashboard-card.component';
5
- import { CardConfig, DashboardConfig, SectionConfig } from '../models';
6
- import { CELL_HEIGHT, COMPACT_BREAKPOINT } from '../models/constants';
7
- import { DashboardSection } from '../section/dashboard-section.component';
8
- import {
9
- Component,
10
- ElementRef,
11
- OnDestroy,
12
- OnInit,
13
- Type,
14
- ViewEncapsulation,
15
- computed,
16
- inject,
17
- input,
18
- linkedSignal,
19
- model,
20
- output,
21
- signal,
22
- viewChild,
23
- } from '@angular/core';
24
- import { Button } from '@fundamental-ngx/ui5-webcomponents/button';
25
- import { Menu } from '@fundamental-ngx/ui5-webcomponents/menu';
26
- import { MenuItem } from '@fundamental-ngx/ui5-webcomponents/menu-item';
27
- import { MenuSeparator } from '@fundamental-ngx/ui5-webcomponents/menu-separator';
28
- import { Text } from '@fundamental-ngx/ui5-webcomponents/text';
29
- import { Title } from '@fundamental-ngx/ui5-webcomponents/title';
30
- import '@ui5/webcomponents-icons/dist/action-settings.js';
31
- import '@ui5/webcomponents-icons/dist/menu2.js';
32
- import { GridStackNode, GridStackOptions } from 'gridstack';
33
- import {
34
- GridstackComponent,
35
- GridstackItemComponent,
36
- nodesCB,
37
- } from 'gridstack/dist/angular';
38
-
39
- document.body.classList.add('ui5-content-density-compact');
40
-
41
- @Component({
42
- selector: 'mfp-dashboard',
43
- imports: [
44
- GridstackComponent,
45
- GridstackItemComponent,
46
- AddCardDialog,
47
- DashboardSection,
48
- DashboardCard,
49
- Button,
50
- Menu,
51
- MenuItem,
52
- MenuSeparator,
53
- Title,
54
- Text,
55
- ],
56
- templateUrl: './dashboard.component.html',
57
- styleUrl: './dashboard.component.scss',
58
- encapsulation: ViewEncapsulation.None,
59
- host: {
60
- '[style.background-image]':
61
- 'config().backgroundImageUrl ? "url(" + config().backgroundImageUrl + ")" : null',
62
- },
63
- })
64
- export class Dashboard implements OnInit, OnDestroy {
65
- static registerAngularComponents(componentTypes: Type<unknown>[]): void {
66
- addComponentToRegistry(componentTypes);
67
- }
68
-
69
- config = input.required<DashboardConfig>();
70
- sections = model<SectionConfig[]>([]);
71
- cards = model<CardConfig[]>([]);
72
- availableCards = input<CardConfig[]>([]);
73
-
74
- readonly saved = output<{ sections: SectionConfig[]; cards: CardConfig[] }>();
75
- readonly actionButtonClick = output<{
76
- event: MouseEvent;
77
- action: ButtonSettings;
78
- }>();
79
-
80
- editMode = signal(false);
81
- compactToolbar = signal(false);
82
- toolbarMenuOpen = signal(false);
83
-
84
- private sectionsSnapshot: SectionConfig[] = [];
85
- private cardsSnapshot: CardConfig[] = [];
86
- private gridStackItems = viewChild.required<GridstackComponent>('grid');
87
- private resizeObserver?: ResizeObserver;
88
- private readonly hostEl = inject(ElementRef<HTMLElement>);
89
-
90
- protected gridOptions = computed(
91
- (): GridStackOptions => ({
92
- cellHeight: CELL_HEIGHT,
93
- disableResize: !this.editMode(),
94
- disableDrag: !this.editMode(),
95
- columnOpts: {
96
- breakpointForWindow: true,
97
- breakpoints: [
98
- { w: 1440, c: 12, layout: 'none' },
99
- { w: 1024, c: 8, layout: 'compact' },
100
- { w: 600, c: 1, layout: 'list' },
101
- ],
102
- },
103
- }),
104
- );
105
-
106
- cardDialogOpen = signal(false);
107
- customActions = computed(() => this.config().customActions ?? []);
108
- addedCardsIds = computed(() => new Set(this.cards().map((c) => c.id)));
109
-
110
- editViewButton = computed(() => ({
111
- icon: 'action-settings',
112
- design: 'Transparent' as const,
113
- tooltip: 'Edit View',
114
- text: '',
115
- ...this.config().buttonsSettings?.editViewButton,
116
- }));
117
-
118
- addCardButton = computed(() => ({
119
- icon: '',
120
- design: 'Default' as const,
121
- tooltip: '',
122
- text: '+ Add Card',
123
- ...this.config().buttonsSettings?.addCardButton,
124
- }));
125
-
126
- sectionCards = computed(() => {
127
- const all = this.cards();
128
- return (sectionId: string) => all.filter((c) => c.sectionId === sectionId);
129
- });
130
-
131
- cardsPosition = new Map<string, { x?: number; y?: number }>();
132
- looseCards = linkedSignal(() => this.cards().filter((c) => !c.sectionId));
133
-
134
- private newGridStackNodes: GridStackNode[] = [];
135
-
136
- ngOnInit(): void {
137
- this.resizeObserver = new ResizeObserver((entries) => {
138
- const width = entries[0]?.contentRect.width ?? 0;
139
- this.compactToolbar.set(width < COMPACT_BREAKPOINT);
140
- });
141
- this.resizeObserver.observe(this.hostEl.nativeElement);
142
- }
143
-
144
- ngOnDestroy(): void {
145
- this.resizeObserver?.disconnect();
146
- }
147
-
148
- onMenuItemClick(actionId: string, event: Event): void {
149
- if (actionId === 'edit-view') {
150
- this.enterEditMode();
151
- return;
152
- }
153
- const action = this.customActions().find((a) => a.action === actionId);
154
- if (action) {
155
- this.actionButtonClick.emit({ event: event as MouseEvent, action });
156
- }
157
- }
158
-
159
- enterEditMode(): void {
160
- const gridStackNodes = this.gridStackItems()
161
- .gridstackItems?.toArray()
162
- .map((node) => node.options);
163
-
164
- if (gridStackNodes) {
165
- this.saveCardsPosition(gridStackNodes);
166
- }
167
-
168
- this.sectionsSnapshot = [...this.sections()];
169
- this.cardsSnapshot = [...this.cards()];
170
- this.editMode.set(true);
171
- }
172
-
173
- saveEdit(): void {
174
- this.saveCardsPosition(this.newGridStackNodes);
175
- this.saved.emit({
176
- sections: this.sections(),
177
- cards: this.cards().map((c) => {
178
- const pos = this.cardsPosition.get(c.id);
179
- return { ...c, x: pos?.x, y: pos?.y };
180
- }),
181
- });
182
- this.editMode.set(false);
183
- }
184
-
185
- cancelEdit(): void {
186
- this.sections.set(this.sectionsSnapshot);
187
- this.cards.set(
188
- this.cardsSnapshot.map((c) => {
189
- const pos = this.cardsPosition.get(c.id);
190
- return { ...c, x: pos?.x, y: pos?.y };
191
- }),
192
- );
193
- this.cardDialogOpen.set(false);
194
- this.editMode.set(false);
195
- }
196
-
197
- removeSection(id: string): void {
198
- this.sections.update((list) => list.filter((s) => s.id !== id));
199
- this.cards.update((list) => list.filter((c) => c.sectionId !== id));
200
- }
201
-
202
- removeCard(id: string): void {
203
- this.cardsPosition.delete(id);
204
- this.cards.update((list) => list.filter((c) => c.id !== id));
205
- }
206
-
207
- openCardPanel(): void {
208
- this.cardDialogOpen.set(true);
209
- }
210
-
211
- closeCardPanel(): void {
212
- this.cardDialogOpen.set(false);
213
- }
214
-
215
- onCardsAdded(cards: CardConfig[]): void {
216
- if (cards.length > 0) {
217
- this.cards.update((list) => [
218
- ...list,
219
- ...cards.map((ac) => ({
220
- ...ac,
221
- })),
222
- ]);
223
- }
224
- this.closeCardPanel();
225
- }
226
-
227
- onOrderChange(event: nodesCB): void {
228
- this.newGridStackNodes = event.nodes;
229
- }
230
-
231
- private saveCardsPosition(items: GridStackNode[]): void {
232
- items.forEach((node) => {
233
- if (node.id) {
234
- this.cardsPosition.set(node.id, { x: node.x, y: node.y });
235
- }
236
- });
237
- }
238
- }
@@ -1 +0,0 @@
1
- export * from './dashboard.component';
@@ -1,5 +0,0 @@
1
- export * from './dashboard';
2
- export * from './add-card-dialog/add-card-dialog.component';
3
- export * from './card/dashboard-card.component';
4
- export * from './section/dashboard-section.component';
5
- export * from './models';
@@ -1,2 +0,0 @@
1
- export const COMPACT_BREAKPOINT = 726;
2
- export const CELL_HEIGHT = 10;