import { Component , EventEmitter , Input , OnDestroy , OnInit , Output , ViewChild , ViewContainerRef , ViewEncapsulation } from '@angular/core' ; import { FormBuilder , FormGroup } from '@angular/forms' ; import { FormlyFieldConfig } from '@ngx-formly/core' ; import { isString } from 'lodash' ; import { merge , NEVER , Observable , Subscription , timer } from 'rxjs' ; import { catchError , debounceTime , map , tap } from 'rxjs/operators' ; import { DragDropService , FieldsService , FieldType , FormlyDesignerService } from './' ; import { ParentService } from './parent.service' ; @ Component ( { selector : 'formly-designer' , template : `
    <form novalidate [formGroup]="form">
      <formly-form *ngIf="fields.length > 0; else placeholder" [options]="options" [model]="model" [form]="form" [fields]="(formlyDesignerService.designerFields$ | async) ?? []">
      </formly-form>
      <ng-template #placeholder>
        <div class="content d-flex justify-content-center align-items-center"
          [ngClass]="{ 'drop-hint': isDragging$ | async, 'drop-target': dropTargetCounter > 0 }"
          (dragenter)="onDragEnter($event)" (dragleave)="onDragLeave()" (dragover)="onDragOver($event)" (drop)="onDrop($event)">
          <div>+</div>
        </div>
      </ng-template>
    </form>
    <!--<div>
      Designer Fields:
      <pre>{{ formlyDesignerService.designerFields$ | async | decycle | json }}</pre>
    </div>-->
  ` , styles : [ `
    formly-designer > form > .content {
      border: 1px dashed #000;
      border-radius: 5px;
      min-height: 2rem;
      padding: 1.5em 1em 0 1em;
      width: 100%;
    }
    formly-designer > form > .content.drop-hint {
      background-color: #e3f2fd;
      border-color: #bbdefb;
    }
    formly-designer > form > .content.drop-target {
      background-color: #f0f4c3;
      border-color: #00c853;
    }
    formly-designer > form > .content > div {
      padding: 2rem;
      padding-bottom: 4rem;
      font-size: 64pt;
      pointer-events: none;
    }
    formly-designer-field-picker .form-group > .input-group > formly-designer-type-select > select {
      border-radius: .25rem 0 0 .25rem;
      border-right: 0;
    }
    formly-designer-wrapper-editor .card > .card-body .form-control {
      width: 100%;
    }
    formly-designer-wrapper-picker .form-group > .input-group > formly-designer-wrapper-select > select {
      border-radius: .25rem 0 0 .25rem;
      border-right: 0;
    }
  ` ] , providers : [ ParentService , ] , encapsulation : ViewEncapsulation . None } ) export class FormlyDesignerComponent implements OnDestroy , OnInit { @ ViewChild ( 'formlyFormContainer' , { read : ViewContainerRef , static : true } ) formlyFormContainer ? : ViewContainerRef ; @ Output ( ) fieldsChange = new EventEmitter < FormlyFieldConfig [ ] > ( ) ; @ Output ( ) modelChange = new EventEmitter < any > ( ) ; types : string [ ] = [ ] ; wrappers : string [ ] = [ ] ; properties : string [ ] = [ ] ; debugFields : FormlyFieldConfig [ ] = [ ] ; isDragging$ : Observable < boolean > ; dropTargetCounter = 0 ; form : FormGroup ; options : any = { } ; get designerId ( ) : string { return '' ; } private readonly subscriptions : Subscription [ ] = [ ] ; constructor ( private dragDropService , private fieldsService , private formBuilder , public formlyDesignerService , private parentService ) { parentService . parent = this ; this . form = this . formBuilder . group ( { } ) ; this . isDragging$ = this . dragDropService . dragging$ . pipe ( map ( dragging => dragging != null ) ) ; } @ Input ( ) get disabled ( ) : boolean { return this . formlyDesignerService . disabled ; } set disabled ( value ) { this . formlyDesignerService . disabled = value ; } @ Input ( ) get fields ( ) : FormlyFieldConfig [ ] { return this . formlyDesignerService . fields ; } set fields ( value ) { this . formlyDesignerService . fields = value ; } @ Input ( ) get model ( ) : any { return this . formlyDesignerService . model ; } set model ( value ) { this . formlyDesignerService . model = value ; } ngOnInit ( ) { this . subscriptions . push ( this . formlyDesignerService . designerFields$ . subscribe ( fields => { this . parentService . clearChildren ( ) ; this . options = { } ; this . form = this . formBuilder . group ( { } ) ; this . fieldsChange . emit ( this . formlyDesignerService . createPrunedFields ( fields , FieldType . Plain ) ) ; } ) , merge ( this . formlyDesignerService . model$ , this . form . valueChanges ) . pipe ( debounceTime ( 50 ) ) . subscribe ( ( ) => this . modelChange . emit ( this . formlyDesignerService . model ) ) ) ; } ngOnDestroy ( ) { this . subscriptions . splice ( 0 ) . forEach ( subscription => subscription . unsubscribe ( ) ) ; } onFieldSelected ( field ) { timer ( 0 ) . pipe ( tap ( ( ) => { if ( this . fieldsService . checkField ( field , this . formlyDesignerService . designerFields ) ) { this . formlyDesignerService . addField ( field ) ; } } ) , catchError ( ( ) => NEVER ) ) . subscribe ( ) ; } onDragEnter ( event ) { event . preventDefault ( ) ; this . dropTargetCounter ++ ; } onDragLeave ( ) { if ( this . dropTargetCounter > 0 ) { this . dropTargetCounter -- ; } } onDragOver ( event ) { event . preventDefault ( ) ; } onDrop ( event ) { if ( this . fields . length ) { return ; } if ( ! isString ( this . dragDropService . dragging ) ) { return ; } this . addChildType ( this . dragDropService . dragging ) ; event . preventDefault ( ) ; } addChildField ( field , index ? ) { this . formlyDesignerService . addField ( field , index ) ; } addChildType ( type , index ? ) { const field = this . formlyDesignerService . createField ( type ) ; this . addChildField ( field , index ) ; } }