Skip to content

Commit

Permalink
feat(Admin UI): basic prototype to manage entity types (#2440)
Browse files Browse the repository at this point in the history
see #2270
[prototype for internal use only]
  • Loading branch information
sleidig authored Jun 28, 2024
1 parent 374bb3c commit 5c4b2a5
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ export class AppComponent {
*/
private detectConfigMode() {
const currentUrl = this.router.url;
this.configFullscreen = currentUrl.startsWith("/admin/entity");
this.configFullscreen = currentUrl.startsWith("/admin/entity/");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<p style="color: red">
This is an internal server admin preview and not stable functionality yet.
</p>

<div class="flex-row gap-regular">
<button mat-raised-button color="accent" (click)="create()">
Add New Entity Type
</button>

<button mat-stroked-button color="accent" (click)="loadEntityTypes(false)">
Also load internal types
</button>
</div>

<table mat-table [dataSource]="entityTypes">
<ng-container matColumnDef="label">
<th mat-header-cell *matHeaderCellDef class="table-header">Entity Type</th>

<td mat-cell *matCellDef="let row">
{{ row.label }}
</td>
</ng-container>

<ng-container matColumnDef="icon">
<th mat-header-cell *matHeaderCellDef class="table-header">Icon</th>

<td mat-cell *matCellDef="let row">
<fa-icon *ngIf="row.icon" [icon]="row.icon"></fa-icon>
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr
mat-row
*matRowDef="let row; columns: columnsToDisplay"
[routerLink]="['/admin', 'entity', row.ENTITY_TYPE]"
style="cursor: pointer"
></tr>
</table>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.table-header {
font-weight: bold;
font-style: italic;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ComponentFixture, TestBed } from "@angular/core/testing";

import { AdminEntityTypesComponent } from "./admin-entity-types.component";
import {
entityRegistry,
EntityRegistry,
} from "../../entity/database-entity.decorator";
import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service";
import { mockEntityMapper } from "../../entity/entity-mapper/mock-entity-mapper-service";

describe("AdminEntityTypesComponent", () => {
let component: AdminEntityTypesComponent;
let fixture: ComponentFixture<AdminEntityTypesComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AdminEntityTypesComponent],
providers: [
{ provide: EntityRegistry, useValue: entityRegistry },
{ provide: EntityMapperService, useValue: mockEntityMapper() },
],
}).compileComponents();

fixture = TestBed.createComponent(AdminEntityTypesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it("should create", () => {
expect(component).toBeTruthy();
});
});
179 changes: 179 additions & 0 deletions src/app/core/admin/admin-entity-types/admin-entity-types.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { Component, OnInit } from "@angular/core";
import { EntityRegistry } from "../../entity/database-entity.decorator";
import {
MatCell,
MatCellDef,
MatColumnDef,
MatHeaderCell,
MatHeaderCellDef,
MatHeaderRow,
MatHeaderRowDef,
MatRow,
MatRowDef,
MatTable,
} from "@angular/material/table";
import { MatButton } from "@angular/material/button";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { NgIf } from "@angular/common";
import { EntityConstructor } from "../../entity/model/entity";
import { RouterLink } from "@angular/router";
import { generateIdFromLabel } from "../../../utils/generate-id-from-label/generate-id-from-label";
import { EntityMapperService } from "../../entity/entity-mapper/entity-mapper.service";
import { EntityConfig } from "../../entity/entity-config";
import { EntityDetailsConfig } from "../../entity-details/EntityDetailsConfig";
import { EntityListConfig } from "../../entity-list/EntityListConfig";
import { Config } from "../../config/config";
import { EntityConfigService } from "../../entity/entity-config.service";
import { DynamicComponentConfig } from "../../config/dynamic-components/dynamic-component-config.interface";

/**
* Manage the configuration of all entity types.
* Currently, this only serves as a utility for internal use, speeding up setup processes.
*
* TODO: This component is only a raw prototype! Needs further concept and implementation.
*/
@Component({
selector: "app-admin-entity-types",
standalone: true,
imports: [
MatHeaderRow,
MatHeaderRowDef,
MatRow,
MatRowDef,
MatCell,
MatHeaderCell,
MatColumnDef,
MatTable,
MatButton,
MatCellDef,
MatHeaderCellDef,
FaIconComponent,
NgIf,
RouterLink,
],
templateUrl: "./admin-entity-types.component.html",
styleUrl: "./admin-entity-types.component.scss",
})
export class AdminEntityTypesComponent implements OnInit {
entityTypes: EntityConstructor[] = [];
columnsToDisplay: string[] = ["label", "icon"];

constructor(
private entities: EntityRegistry,
private entityMapper: EntityMapperService,
) {}

ngOnInit() {
this.loadEntityTypes();
}

protected loadEntityTypes(onlyUserFacing = true) {
this.entityTypes = this.entities
.getEntityTypes(onlyUserFacing)
.map((e) => e.value);
}

async create() {
const name = prompt("Please enter entity type name:");
if (!name) {
return;
}
const id = generateIdFromLabel(name);
if (this.entityTypeExists(id)) {
alert("Entity type already exists.");
return;
}

// save default entity schema
await this.saveDefaultEntityConfig(
id,
this.getDefaultEntityConfig(id, name),
this.getDefaultDetailsViewConfig(id),
this.getDefaultListViewConfig(id),
);
}

private entityTypeExists(id: string) {
return Array.from(this.entities.keys()).some(
(key) => key.toLowerCase() === id.toLowerCase(),
);
}

private async saveDefaultEntityConfig(
id: string,
entityConfig: EntityConfig,
detailsViewConfig: DynamicComponentConfig,
listViewConfig: DynamicComponentConfig,
) {
const originalConfig = await this.entityMapper.load(
Config,
Config.CONFIG_KEY,
);
const newConfig = originalConfig.copy();

newConfig.data[EntityConfigService.PREFIX_ENTITY_CONFIG + id] =
entityConfig;
newConfig.data[EntityConfigService.getDetailsViewId(entityConfig)] =
detailsViewConfig;
newConfig.data[EntityConfigService.getListViewId(entityConfig)] =
listViewConfig;

await this.entityMapper.save(newConfig);
}

private getDefaultEntityConfig(
entityTypeId: string,
name: string,
): EntityConfig {
return {
label: name,
route: entityTypeId,
};
}

private getDefaultDetailsViewConfig(
entityType: string,
): DynamicComponentConfig<EntityDetailsConfig> {
return {
component: "EntityDetails",
config: {
entityType: entityType,
panels: [
{
title: "Basic Information",
components: [
{
title: "",
component: "Form",
config: {
fieldGroups: [{ fields: [] }],
},
},
],
},
],
},
};
}

private getDefaultListViewConfig(
entityType: string,
): DynamicComponentConfig<EntityListConfig> {
return {
component: "EntityList",
config: {
entityType: entityType,
columnGroups: {
default: "Overview",
mobile: "Overview",
groups: [
{
name: "Overview",
columns: [],
},
],
},
},
};
}
}
7 changes: 7 additions & 0 deletions src/app/core/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ export class AdminModule {
(c) => c.AdminEntityComponent,
),
],
[
"AdminEntityTypes",
() =>
import("./admin-entity-types/admin-entity-types.component").then(
(c) => c.AdminEntityTypesComponent,
),
],
]);
}
}
10 changes: 10 additions & 0 deletions src/app/core/admin/admin.routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ export const adminRoutes: Routes = [
path: "setup-wizard",
component: SetupWizardComponent,
},
{
path: "entity",
component: RoutedViewComponent,
data: {
component: "AdminEntityTypes",
entity: "Config",
requiredPermissionOperation: "update",
},
canActivate: [EntityPermissionGuard],
},
{
path: "entity/:entityType",
component: RoutedViewComponent,
Expand Down
4 changes: 4 additions & 0 deletions src/app/core/admin/admin/admin.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ <h2>Shortcuts</h2>
Database Conflicts
</mat-list-item>

<mat-list-item [routerLink]="['/admin/entity']" i18n="admin menu item">
Administer Entity Types
</mat-list-item>

<mat-list-item [routerLink]="['/admin/setup-wizard']" i18n="admin menu item">
Setup Wizard
</mat-list-item>
Expand Down
8 changes: 4 additions & 4 deletions src/app/core/entity/entity-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export class EntityConfigService {
/** original initial entity schemas without overrides from config */
private coreEntitySchemas = new Map<string, EntitySchema>();

static getDetailsViewId(entityConstructor: EntityConstructor) {
return this.getListViewId(entityConstructor) + "/:id";
static getDetailsViewId(entityConfig: EntityConfig) {
return this.getListViewId(entityConfig) + "/:id";
}
static getListViewId(entityConstructor: EntityConstructor) {
return PREFIX_VIEW_CONFIG + entityConstructor.route.replace(/^\//, "");
static getListViewId(entityConfig: EntityConfig) {
return PREFIX_VIEW_CONFIG + entityConfig.route.replace(/^\//, "");
}

// TODO: merge with EntityRegistry?
Expand Down

0 comments on commit 5c4b2a5

Please sign in to comment.