Skip to content

Commit b53deb3

Browse files
authored
Merge pull request #2333 from Aam-Digital/master
Release 3.34.2
2 parents 62abb1e + e77e385 commit b53deb3

File tree

16 files changed

+183
-35
lines changed

16 files changed

+183
-35
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/core/admin/admin-entity-details/admin-entity-field/admin-entity-field.component.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ describe("AdminEntityFieldComponent", () => {
161161
generateIdFromLabel("test label"),
162162
);
163163
}));
164+
it("should generate manually created 'additional' value for configurable-enum", fakeAsync(() => {
165+
const dataTypeForm = component.schemaFieldsForm.get("dataType");
166+
dataTypeForm.setValue(ConfigurableEnumDatatype.dataType);
167+
tick();
168+
169+
let newAdditional;
170+
component
171+
.createNewAdditionalOptionAsync("newEnumId")
172+
.then((result) => (newAdditional = result));
173+
tick();
174+
175+
expect(newAdditional).toEqual({
176+
label: "newEnumId",
177+
value: "newEnumId",
178+
});
179+
}));
164180

165181
it("should init 'additional' options for entity datatypes", fakeAsync(() => {
166182
const mockEntityTypes = [Entity, RecurringActivity];

src/app/core/admin/admin-entity-details/admin-entity-field/admin-entity-field.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ export class AdminEntityFieldComponent implements OnChanges {
198198
objectToValue = (v: SimpleDropdownValue) => v?.value;
199199
createNewAdditionalOption: (input: string) => SimpleDropdownValue;
200200
createNewAdditionalOptionAsync = async (input) =>
201-
this.createNewAdditionalOptionAsync(input);
201+
this.createNewAdditionalOption(input);
202202

203203
private updateDataTypeAdditional(dataType: string) {
204204
this.resetAdditional();

src/app/core/admin/admin-entity/admin-entity.component.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import { FontAwesomeTestingModule } from "@fortawesome/angular-fontawesome/testi
2929
import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service";
3030
import { EntityActionsService } from "../../entity/entity-actions/entity-actions.service";
3131
import { EntitySchemaField } from "../../entity/schema/entity-schema-field";
32+
import { ActivatedRoute } from "@angular/router";
33+
import { of } from "rxjs";
3234

3335
describe("AdminEntityComponent", () => {
3436
let component: AdminEntityComponent;
@@ -59,7 +61,9 @@ describe("AdminEntityComponent", () => {
5961
[viewConfigId]: { component: "EntityDetails", config: viewConfig },
6062
[entityConfigId]: {},
6163
};
62-
64+
const mockActivatedRoute = {
65+
queryParams: of({ mode: "list" }),
66+
};
6367
mockConfigService = jasmine.createSpyObj(["getConfig"]);
6468
mockConfigService.getConfig.and.returnValue(config[viewConfigId]);
6569

@@ -88,6 +92,10 @@ describe("AdminEntityComponent", () => {
8892
provide: EntityActionsService,
8993
useValue: jasmine.createSpyObj(["showSnackbarConfirmationWithUndo"]),
9094
},
95+
{
96+
provide: ActivatedRoute,
97+
useValue: mockActivatedRoute,
98+
},
9199
],
92100
});
93101
fixture = TestBed.createComponent(AdminEntityComponent);

src/app/core/admin/admin-entity/admin-entity.component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
MatSidenavContent,
2929
} from "@angular/material/sidenav";
3030
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
31-
import { RouterLink } from "@angular/router";
31+
import { RouterLink, ActivatedRoute } from "@angular/router";
3232
import { MatListItem, MatNavList } from "@angular/material/list";
3333
import { AdminEntityDetailsComponent } from "../admin-entity-details/admin-entity-details/admin-entity-details.component";
3434

@@ -72,10 +72,18 @@ export class AdminEntityComponent implements OnInit {
7272
private location: Location,
7373
private entityMapper: EntityMapperService,
7474
private entityActionsService: EntityActionsService,
75+
private routes: ActivatedRoute,
7576
) {}
7677

7778
ngOnInit(): void {
7879
this.init();
80+
this.routes.queryParams.subscribe((params) => {
81+
if (params.mode === "details") {
82+
this.mode = "details";
83+
} else if (params.mode === "list") {
84+
this.mode = "list";
85+
}
86+
});
7987
}
8088

8189
private init() {

src/app/core/admin/building-blocks/admin-tabs/admin-tabs.component.html

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
11
<mat-tab-group [preserveContent]="true" #matTabGroup>
22
<mat-tab *ngFor="let tab of tabs; index as tabIndex">
33
<ng-template mat-tab-label>
4-
@if (tabIndex === matTabGroup.selectedIndex) {
5-
<app-admin-section-header
6-
[(title)]="tab['title'] ?? tab['name']"
7-
(remove)="
8-
tabs.splice(tabIndex, 1); matTabGroup.selectedIndex = tabIndex - 1
9-
"
10-
></app-admin-section-header>
11-
} @else {
12-
<!-- only current tab can be renamed -->
13-
{{ tab["title"] ?? tab["name"] }}
14-
}
4+
<div
5+
class="drop-list flex-row"
6+
[id]="'tabs-' + tabIndex"
7+
cdkDropList
8+
cdkDropListOrientation="horizontal"
9+
(cdkDropListDropped)="drop($event)"
10+
[cdkDropListConnectedTo]="getAllTabs(tabIndex)"
11+
>
12+
<div
13+
cdkDrag
14+
cdkDragLockAxis="x"
15+
class="flex-row align-center drop-item gap-small"
16+
>
17+
<fa-icon icon="grip-vertical" size="xl" class="drag-handle"></fa-icon>
18+
@if (tabIndex === matTabGroup.selectedIndex) {
19+
<app-admin-section-header
20+
[(title)]="tab['title'] ?? tab['name']"
21+
(remove)="
22+
tabs.splice(tabIndex, 1);
23+
matTabGroup.selectedIndex = tabIndex - 1
24+
"
25+
></app-admin-section-header>
26+
} @else {
27+
<!-- only current tab can be renamed -->
28+
{{ tab["title"] ?? tab["name"] }}
29+
}
30+
</div>
31+
</div>
1532
</ng-template>
1633

1734
<ng-template matTabContent>

src/app/core/admin/building-blocks/admin-tabs/admin-tabs.component.scss

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@use "variables/colors";
12

23
:host ::ng-deep .mat-mdc-tab {
34
// adjust tab header height to properly display mat-form-field for editing tab label
@@ -6,3 +7,28 @@
67
app-admin-section-header {
78
margin-bottom: -1em;
89
}
10+
.drag-handle {
11+
cursor: move;
12+
margin: auto;
13+
color: colors.$accent;
14+
}
15+
16+
.cdk-drag-preview {
17+
box-sizing: border-box;
18+
border-radius: 4px;
19+
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
20+
0 8px 10px 1px rgba(0, 0, 0, 0.14),
21+
0 3px 14px 2px rgba(0, 0, 0, 0.12);
22+
}
23+
24+
.cdk-drag-placeholder {
25+
opacity: 0;
26+
}
27+
28+
.cdk-drag-animating {
29+
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
30+
}
31+
32+
.drop-list.cdk-drop-list-dragging .drop-item:not(.cdk-drag-placeholder) {
33+
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
34+
}

src/app/core/admin/building-blocks/admin-tabs/admin-tabs.component.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import {
1919
} from "@angular/material/tabs";
2020
import { MatTooltip } from "@angular/material/tooltip";
2121
import { AdminTabTemplateDirective } from "./admin-tab-template.directive";
22+
import {
23+
CdkDragDrop,
24+
DragDropModule,
25+
moveItemInArray,
26+
} from "@angular/cdk/drag-drop";
2227

2328
/**
2429
* Building block for drag&drop form builder to let an admin user manage multiple tabs.
@@ -50,6 +55,7 @@ import { AdminTabTemplateDirective } from "./admin-tab-template.directive";
5055
MatTabLabel,
5156
MatTooltip,
5257
AdminTabTemplateDirective,
58+
DragDropModule,
5359
],
5460
templateUrl: "./admin-tabs.component.html",
5561
styleUrl: "./admin-tabs.component.scss",
@@ -76,4 +82,40 @@ export class AdminTabsComponent<
7682
this.tabGroup.focusTab(newTabIndex);
7783
});
7884
}
85+
86+
/**
87+
* A list of tab element ids required for linking drag&drop targets
88+
* due to the complex template of tab headers.
89+
* @param index
90+
*/
91+
getAllTabs(index: number) {
92+
const allTabs = [];
93+
for (let i = 0; i < this.tabs?.length; i++) {
94+
if (i != index) {
95+
allTabs.push("tabs-" + i);
96+
}
97+
}
98+
99+
return allTabs;
100+
}
101+
102+
drop(event: CdkDragDrop<string[]>) {
103+
const previousIndex = parseInt(
104+
event.previousContainer.id.replace("tabs-", ""),
105+
);
106+
const currentIndex = parseInt(event.container.id.replace("tabs-", ""));
107+
108+
const previouslySelectedTab = this.tabs[this.tabGroup.selectedIndex];
109+
110+
moveItemInArray(this.tabs, previousIndex, currentIndex);
111+
112+
// re-select the previously selected tab, even after its index shifted
113+
let shiftedSelectedIndex = this.tabs.indexOf(previouslySelectedTab);
114+
if (shiftedSelectedIndex !== this.tabGroup.selectedIndex) {
115+
this.tabGroup.selectedIndex = shiftedSelectedIndex;
116+
this.tabGroup.focusTab(shiftedSelectedIndex);
117+
}
118+
119+
this.tabs = JSON.parse(JSON.stringify(this.tabs)); // Needed to avoid Angular Ivy render bug
120+
}
79121
}

src/app/core/database/sync.service.spec.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe("SyncService", () => {
6363
tick(SyncService.SYNC_INTERVAL);
6464
}));
6565

66-
it("should try auto-login if fetch fails and fetch again", async () => {
66+
it("should try auto-login if fetch fails and fetch again", fakeAsync(() => {
6767
// Make sync call pass
6868
spyOn(
6969
TestBed.inject(Database) as PouchDatabase,
@@ -83,13 +83,17 @@ describe("SyncService", () => {
8383
});
8484
mockAuthService.login.and.resolveTo();
8585
const initSpy = spyOn(service["remoteDatabase"], "initRemoteDB");
86-
await service.startSync();
86+
service.startSync();
87+
tick();
8788
// taking fetch function from init call
8889
const fetch = initSpy.calls.mostRecent().args[1];
8990

9091
const url = "/db/_changes";
9192
const opts = { headers: {} };
92-
await expectAsync(fetch(url, opts)).toBeResolved();
93+
let fetchResult;
94+
fetch(url, opts).then((res) => (fetchResult = res));
95+
tick();
96+
expect(fetchResult).toBeDefined();
9397

9498
expect(PouchDB.fetch).toHaveBeenCalledTimes(2);
9599
expect(PouchDB.fetch).toHaveBeenCalledWith(url, opts);
@@ -98,5 +102,6 @@ describe("SyncService", () => {
98102
expect(mockAuthService.addAuthHeader).toHaveBeenCalledTimes(2);
99103

100104
service.liveSyncEnabled = false;
101-
});
105+
tick(SyncService.SYNC_INTERVAL);
106+
}));
102107
});

src/app/core/entity-details/entity-details/entity-details.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
<button
3333
mat-menu-item
3434
[routerLink]="['/admin/entity', record?.getType()]"
35+
[queryParams]="{ mode: 'details' }"
36+
queryParamsHandling="merge"
3537
*ngIf="'update' | ablePure: 'Config' | async"
3638
>
3739
<fa-icon

0 commit comments

Comments
 (0)