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

Metadata

Relationships

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor()

Inputs

config
Type : EntityDetailsConfig
Required :  true
entityConstructor
Type : EntityConstructor
Required :  true

Outputs

relatedEntityModified
Type : EntityConstructor

Event emitted when a related entity's schema has been modified, to ensure changes are saved centrally by parent component.

Methods

addComponent
addComponent(panel: Panel)
Parameters :
Name Type Optional
panel Panel No
Returns : void
newPanelFactory
newPanelFactory()
Returns : Panel
onSectionDrop
onSectionDrop(panel: Panel, event: CdkDragDrop)
Parameters :
Name Type Optional
panel Panel No
event CdkDragDrop<PanelComponent[]> No
Returns : void
removeComponent
removeComponent(panel: Panel, index: number)
Parameters :
Name Type Optional
panel Panel No
index number No
Returns : void

Properties

panels
Type : unknown
Default value : linkedSignal(() => this.config()?.panels ?? [])
import {
  Component,
  effect,
  inject,
  input,
  output,
  ChangeDetectionStrategy,
  linkedSignal,
} from "@angular/core";
import {
  EntityDetailsConfig,
  Panel,
  PanelComponent,
} from "../../../entity-details/EntityDetailsConfig";
import { EntityConstructor } from "../../../entity/model/entity";
import { DynamicComponent } from "../../../config/dynamic-components/dynamic-component.decorator";
import { MatTabsModule } from "@angular/material/tabs";
import { FaIconComponent } from "@fortawesome/angular-fontawesome";
import { MatButtonModule } from "@angular/material/button";
import { ViewTitleComponent } from "../../../common-components/view-title/view-title.component";
import { AdminSectionHeaderComponent } from "../../building-blocks/admin-section-header/admin-section-header.component";
import { AdminEntityFormComponent } from "../admin-entity-form/admin-entity-form.component";
import { AdminEntityPanelComponentComponent } from "../admin-entity-panel-component/admin-entity-panel-component.component";
import { MatTooltipModule } from "@angular/material/tooltip";
import { AdminTabsComponent } from "../../building-blocks/admin-tabs/admin-tabs.component";
import { AdminTabTemplateDirective } from "../../building-blocks/admin-tabs/admin-tab-template.directive";
import { MatDialog } from "@angular/material/dialog";
import { MatExpansionModule } from "@angular/material/expansion";
import { MatIconModule } from "@angular/material/icon";
import {
  CdkDragDrop,
  DragDropModule,
  moveItemInArray,
} from "@angular/cdk/drag-drop";
import { WidgetComponentSelectComponent } from "#src/app/core/admin/admin-entity-details/widget-component-select/widget-component-select.component";
import { HintBoxComponent } from "#src/app/core/common-components/hint-box/hint-box.component";

@DynamicComponent("AdminEntityDetails")
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: "app-admin-entity-details",
  templateUrl: "./admin-entity-details.component.html",
  styleUrls: ["./admin-entity-details.component.scss"],
  imports: [
    MatTabsModule,
    FaIconComponent,
    MatButtonModule,
    ViewTitleComponent,
    AdminSectionHeaderComponent,
    AdminEntityFormComponent,
    AdminEntityPanelComponentComponent,
    MatTooltipModule,
    AdminTabsComponent,
    AdminTabTemplateDirective,
    MatExpansionModule,
    MatIconModule,
    DragDropModule,
    HintBoxComponent,
  ],
})
export class AdminEntityDetailsComponent {
  private dialog = inject(MatDialog);

  entityConstructor = input.required<EntityConstructor>();
  config = input.required<EntityDetailsConfig>();

  panels = linkedSignal(() => this.config()?.panels ?? []);

  constructor() {
    effect(() => {
      this.config().panels = this.panels();
    });
  }

  /**
   * Event emitted when a related entity's schema has been modified,
   * to ensure changes are saved centrally by parent component.
   */
  relatedEntityModified = output<EntityConstructor>();

  newPanelFactory(): Panel {
    return { title: "New Tab", components: [] };
  }

  addComponent(panel: Panel) {
    this.dialog
      .open(WidgetComponentSelectComponent, {
        data: { entityType: this.entityConstructor().ENTITY_TYPE },
      })
      .afterClosed()
      .subscribe((sectionConfig: PanelComponent) => {
        if (!sectionConfig) return;
        this.panels.update((panels) =>
          panels.map((p) =>
            p === panel
              ? { ...p, components: [...p.components, sectionConfig] }
              : p,
          ),
        );
      });
  }

  removeComponent(panel: Panel, index: number) {
    this.panels.update((panels) =>
      panels.map((p) =>
        p === panel
          ? { ...p, components: p.components.filter((_, i) => i !== index) }
          : p,
      ),
    );
  }

  onSectionDrop(panel: Panel, event: CdkDragDrop<PanelComponent[]>) {
    if (
      event.previousIndex === event.currentIndex ||
      event.currentIndex < 0 ||
      event.currentIndex >= panel.components.length
    ) {
      return;
    }

    const components = [...panel.components];
    moveItemInArray(components, event.previousIndex, event.currentIndex);
    this.panels.update((panels) =>
      panels.map((p) => (p === panel ? { ...p, components } : p)),
    );
  }
}
<app-hint-box i18n>
  You can edit how users will see the details of a single record of this type.
  Drag and drop fields and sections in this preview of a profile view and group
  them as appropriate. The editor below closely resembles how the form will look
  for users later.
  <br />
  We recommend to keep things simple: Only add fields that you really need for
  your work.
</app-hint-box>

<app-view-title [disableBackButton]="true" [displayInPlace]="true">
  <span i18n>&lt;Name of displayed {{ entityConstructor()?.label }}&gt;</span>
</app-view-title>

<app-admin-tabs
  [tabs]="panels()"
  (tabsChange)="panels.set($event)"
  [newTabFactory]="newPanelFactory"
>
  <ng-template [appAdminTabTemplate]="panels()" let-item let-tabIndex="index">
    <div class="flex-column gap-large padding-top-large">
      <mat-accordion
        cdkDropList
        [cdkDropListData]="item.components"
        cdkDropListOrientation="vertical"
        (cdkDropListDropped)="onSectionDrop(item, $event)"
      >
        @for (
          componentConfig of item.components;
          track componentConfig;
          let i = $index
        ) {
          <mat-expansion-panel
            class="section-wrapper"
            [expanded]="item.components.length <= 1"
            cdkDrag
            cdkDragLockAxis="y"
          >
            <mat-expansion-panel-header>
              <div class="section-header-row">
                <fa-icon
                  icon="grip-vertical"
                  size="xl"
                  class="drag-handle"
                ></fa-icon>
                <div class="section-header-content">
                  <app-admin-section-header
                    [(title)]="componentConfig.title"
                    (remove)="removeComponent(item, i)"
                    (keydown)="$event.stopPropagation()"
                  ></app-admin-section-header>
                </div>
              </div>
            </mat-expansion-panel-header>
            <div class="section-content">
              @if (componentConfig.component === "Form") {
                <app-admin-entity-form
                  [config]="componentConfig.config"
                  (configChange)="componentConfig.config = $event"
                  [entityType]="entityConstructor()"
                  uniqueAreaId="{{ tabIndex }}-{{ i }}"
                >
                </app-admin-entity-form>
              } @else {
                <app-admin-entity-panel-component
                  [config]="componentConfig"
                  [entityType]="entityConstructor()"
                  (relatedEntityModified)="relatedEntityModified.emit($event)"
                ></app-admin-entity-panel-component>
              }
            </div>
          </mat-expansion-panel>
        }
      </mat-accordion>

      <button
        mat-stroked-button
        color="accent"
        class="section-add-button"
        (click)="addComponent(item)"
        i18n
      >
        <fa-icon
          aria-hidden="true"
          icon="plus-circle"
          class="standard-icon-with-text"
        ></fa-icon>
        Add Section
      </button>
    </div>
  </ng-template>
</app-admin-tabs>

./admin-entity-details.component.scss

@use "@angular/material/core/style/elevation" as mat-elevation;
@use "variables/sizes";
@use "variables/colors";

.admin-ui-title {
  font-style: italic;
}

.save-buttons {
  margin: auto 0;
}

.section-wrapper {
  @include mat-elevation.elevation(1);
  margin: sizes.$small;
  padding: sizes.$small;

  .mat-expansion-panel-header {
    min-height: 70px;
    align-items: center;
  }
}
/* TODO: making direct child of app-admin-section-header somehow breaks this
   although `:has(> app-admin-section-header .group-remove-button` (without :hover) does work ... */
.section-wrapper:has(
  .section-header-content app-admin-section-header .group-remove-button:hover
) {
  border-color: rgb(255, 0, 0);
  background-color: rgba(255, 0, 0, 0.1);
}

.section-header-row {
  display: flex;
  align-items: center;
  gap: sizes.$small;
  width: 100%;
}

.section-header-content {
  flex: 1;
  padding-right: sizes.$large;
}

.section-add-button {
  padding: sizes.$large;
  margin: sizes.$small;
}

.section-content {
  overflow-x: auto;
  overflow-y: hidden;
}

.drag-handle {
  color: colors.$accent;
  cursor: move;
  min-width: 2em;
  text-align: center;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""