Skip to content

Commit 5c4b2a5

Browse files
authored
feat(Admin UI): basic prototype to manage entity types (#2440)
see #2270 [prototype for internal use only]
1 parent 374bb3c commit 5c4b2a5

File tree

9 files changed

+280
-5
lines changed

9 files changed

+280
-5
lines changed

src/app/app.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ export class AppComponent {
4747
*/
4848
private detectConfigMode() {
4949
const currentUrl = this.router.url;
50-
this.configFullscreen = currentUrl.startsWith("/admin/entity");
50+
this.configFullscreen = currentUrl.startsWith("/admin/entity/");
5151
}
5252
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<p style="color: red">
2+
This is an internal server admin preview and not stable functionality yet.
3+
</p>
4+
5+
<div class="flex-row gap-regular">
6+
<button mat-raised-button color="accent" (click)="create()">
7+
Add New Entity Type
8+
</button>
9+
10+
<button mat-stroked-button color="accent" (click)="loadEntityTypes(false)">
11+
Also load internal types
12+
</button>
13+
</div>
14+
15+
<table mat-table [dataSource]="entityTypes">
16+
<ng-container matColumnDef="label">
17+
<th mat-header-cell *matHeaderCellDef class="table-header">Entity Type</th>
18+
19+
<td mat-cell *matCellDef="let row">
20+
{{ row.label }}
21+
</td>
22+
</ng-container>
23+
24+
<ng-container matColumnDef="icon">
25+
<th mat-header-cell *matHeaderCellDef class="table-header">Icon</th>
26+
27+
<td mat-cell *matCellDef="let row">
28+
<fa-icon *ngIf="row.icon" [icon]="row.icon"></fa-icon>
29+
</td>
30+
</ng-container>
31+
32+
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
33+
<tr
34+
mat-row
35+
*matRowDef="let row; columns: columnsToDisplay"
36+
[routerLink]="['/admin', 'entity', row.ENTITY_TYPE]"
37+
style="cursor: pointer"
38+
></tr>
39+
</table>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.table-header {
2+
font-weight: bold;
3+
font-style: italic;
4+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ComponentFixture, TestBed } from "@angular/core/testing";
2+
3+
import { AdminEntityTypesComponent } from "./admin-entity-types.component";
4+
import {
5+
entityRegistry,
6+
EntityRegistry,
7+
} from "../../entity/database-entity.decorator";
8+
import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service";
9+
import { mockEntityMapper } from "../../entity/entity-mapper/mock-entity-mapper-service";
10+
11+
describe("AdminEntityTypesComponent", () => {
12+
let component: AdminEntityTypesComponent;
13+
let fixture: ComponentFixture<AdminEntityTypesComponent>;
14+
15+
beforeEach(async () => {
16+
await TestBed.configureTestingModule({
17+
imports: [AdminEntityTypesComponent],
18+
providers: [
19+
{ provide: EntityRegistry, useValue: entityRegistry },
20+
{ provide: EntityMapperService, useValue: mockEntityMapper() },
21+
],
22+
}).compileComponents();
23+
24+
fixture = TestBed.createComponent(AdminEntityTypesComponent);
25+
component = fixture.componentInstance;
26+
fixture.detectChanges();
27+
});
28+
29+
it("should create", () => {
30+
expect(component).toBeTruthy();
31+
});
32+
});
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { Component, OnInit } from "@angular/core";
2+
import { EntityRegistry } from "../../entity/database-entity.decorator";
3+
import {
4+
MatCell,
5+
MatCellDef,
6+
MatColumnDef,
7+
MatHeaderCell,
8+
MatHeaderCellDef,
9+
MatHeaderRow,
10+
MatHeaderRowDef,
11+
MatRow,
12+
MatRowDef,
13+
MatTable,
14+
} from "@angular/material/table";
15+
import { MatButton } from "@angular/material/button";
16+
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
17+
import { NgIf } from "@angular/common";
18+
import { EntityConstructor } from "../../entity/model/entity";
19+
import { RouterLink } from "@angular/router";
20+
import { generateIdFromLabel } from "../../../utils/generate-id-from-label/generate-id-from-label";
21+
import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service";
22+
import { EntityConfig } from "../../entity/entity-config";
23+
import { EntityDetailsConfig } from "../../entity-details/EntityDetailsConfig";
24+
import { EntityListConfig } from "../../entity-list/EntityListConfig";
25+
import { Config } from "../../config/config";
26+
import { EntityConfigService } from "../../entity/entity-config.service";
27+
import { DynamicComponentConfig } from "../../config/dynamic-components/dynamic-component-config.interface";
28+
29+
/**
30+
* Manage the configuration of all entity types.
31+
* Currently, this only serves as a utility for internal use, speeding up setup processes.
32+
*
33+
* TODO: This component is only a raw prototype! Needs further concept and implementation.
34+
*/
35+
@Component({
36+
selector: "app-admin-entity-types",
37+
standalone: true,
38+
imports: [
39+
MatHeaderRow,
40+
MatHeaderRowDef,
41+
MatRow,
42+
MatRowDef,
43+
MatCell,
44+
MatHeaderCell,
45+
MatColumnDef,
46+
MatTable,
47+
MatButton,
48+
MatCellDef,
49+
MatHeaderCellDef,
50+
FaIconComponent,
51+
NgIf,
52+
RouterLink,
53+
],
54+
templateUrl: "./admin-entity-types.component.html",
55+
styleUrl: "./admin-entity-types.component.scss",
56+
})
57+
export class AdminEntityTypesComponent implements OnInit {
58+
entityTypes: EntityConstructor[] = [];
59+
columnsToDisplay: string[] = ["label", "icon"];
60+
61+
constructor(
62+
private entities: EntityRegistry,
63+
private entityMapper: EntityMapperService,
64+
) {}
65+
66+
ngOnInit() {
67+
this.loadEntityTypes();
68+
}
69+
70+
protected loadEntityTypes(onlyUserFacing = true) {
71+
this.entityTypes = this.entities
72+
.getEntityTypes(onlyUserFacing)
73+
.map((e) => e.value);
74+
}
75+
76+
async create() {
77+
const name = prompt("Please enter entity type name:");
78+
if (!name) {
79+
return;
80+
}
81+
const id = generateIdFromLabel(name);
82+
if (this.entityTypeExists(id)) {
83+
alert("Entity type already exists.");
84+
return;
85+
}
86+
87+
// save default entity schema
88+
await this.saveDefaultEntityConfig(
89+
id,
90+
this.getDefaultEntityConfig(id, name),
91+
this.getDefaultDetailsViewConfig(id),
92+
this.getDefaultListViewConfig(id),
93+
);
94+
}
95+
96+
private entityTypeExists(id: string) {
97+
return Array.from(this.entities.keys()).some(
98+
(key) => key.toLowerCase() === id.toLowerCase(),
99+
);
100+
}
101+
102+
private async saveDefaultEntityConfig(
103+
id: string,
104+
entityConfig: EntityConfig,
105+
detailsViewConfig: DynamicComponentConfig,
106+
listViewConfig: DynamicComponentConfig,
107+
) {
108+
const originalConfig = await this.entityMapper.load(
109+
Config,
110+
Config.CONFIG_KEY,
111+
);
112+
const newConfig = originalConfig.copy();
113+
114+
newConfig.data[EntityConfigService.PREFIX_ENTITY_CONFIG + id] =
115+
entityConfig;
116+
newConfig.data[EntityConfigService.getDetailsViewId(entityConfig)] =
117+
detailsViewConfig;
118+
newConfig.data[EntityConfigService.getListViewId(entityConfig)] =
119+
listViewConfig;
120+
121+
await this.entityMapper.save(newConfig);
122+
}
123+
124+
private getDefaultEntityConfig(
125+
entityTypeId: string,
126+
name: string,
127+
): EntityConfig {
128+
return {
129+
label: name,
130+
route: entityTypeId,
131+
};
132+
}
133+
134+
private getDefaultDetailsViewConfig(
135+
entityType: string,
136+
): DynamicComponentConfig<EntityDetailsConfig> {
137+
return {
138+
component: "EntityDetails",
139+
config: {
140+
entityType: entityType,
141+
panels: [
142+
{
143+
title: "Basic Information",
144+
components: [
145+
{
146+
title: "",
147+
component: "Form",
148+
config: {
149+
fieldGroups: [{ fields: [] }],
150+
},
151+
},
152+
],
153+
},
154+
],
155+
},
156+
};
157+
}
158+
159+
private getDefaultListViewConfig(
160+
entityType: string,
161+
): DynamicComponentConfig<EntityListConfig> {
162+
return {
163+
component: "EntityList",
164+
config: {
165+
entityType: entityType,
166+
columnGroups: {
167+
default: "Overview",
168+
mobile: "Overview",
169+
groups: [
170+
{
171+
name: "Overview",
172+
columns: [],
173+
},
174+
],
175+
},
176+
},
177+
};
178+
}
179+
}

src/app/core/admin/admin.module.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ export class AdminModule {
3030
(c) => c.AdminEntityComponent,
3131
),
3232
],
33+
[
34+
"AdminEntityTypes",
35+
() =>
36+
import("./admin-entity-types/admin-entity-types.component").then(
37+
(c) => c.AdminEntityTypesComponent,
38+
),
39+
],
3340
]);
3441
}
3542
}

src/app/core/admin/admin.routing.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ export const adminRoutes: Routes = [
2020
path: "setup-wizard",
2121
component: SetupWizardComponent,
2222
},
23+
{
24+
path: "entity",
25+
component: RoutedViewComponent,
26+
data: {
27+
component: "AdminEntityTypes",
28+
entity: "Config",
29+
requiredPermissionOperation: "update",
30+
},
31+
canActivate: [EntityPermissionGuard],
32+
},
2333
{
2434
path: "entity/:entityType",
2535
component: RoutedViewComponent,

src/app/core/admin/admin/admin.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ <h2>Shortcuts</h2>
1717
Database Conflicts
1818
</mat-list-item>
1919

20+
<mat-list-item [routerLink]="['/admin/entity']" i18n="admin menu item">
21+
Administer Entity Types
22+
</mat-list-item>
23+
2024
<mat-list-item [routerLink]="['/admin/setup-wizard']" i18n="admin menu item">
2125
Setup Wizard
2226
</mat-list-item>

src/app/core/entity/entity-config.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ export class EntityConfigService {
2929
/** original initial entity schemas without overrides from config */
3030
private coreEntitySchemas = new Map<string, EntitySchema>();
3131

32-
static getDetailsViewId(entityConstructor: EntityConstructor) {
33-
return this.getListViewId(entityConstructor) + "/:id";
32+
static getDetailsViewId(entityConfig: EntityConfig) {
33+
return this.getListViewId(entityConfig) + "/:id";
3434
}
35-
static getListViewId(entityConstructor: EntityConstructor) {
36-
return PREFIX_VIEW_CONFIG + entityConstructor.route.replace(/^\//, "");
35+
static getListViewId(entityConfig: EntityConfig) {
36+
return PREFIX_VIEW_CONFIG + entityConfig.route.replace(/^\//, "");
3737
}
3838

3939
// TODO: merge with EntityRegistry?

0 commit comments

Comments
 (0)