searchCustomer(searchString: any) {
    let val = searchString.target.value;
    if (!val || !val.trim()) {
      this.customers = this.storeCustomers;
      return;
    }
    this.customers = this.filterCustomer({
      NAME: val
    });
  }


filterCustomer(params?: any) {
    if (!params) {
      return this.storeCustomers;
    }

    return this.customers.filter((item) => {
      for (let key in params) {
        let field = item[key];
        if (typeof field == 'string' && field.toLowerCase().indexOf(params[key].toLowerCase()) >= 0) {
          return item;
        } else if (field == params[key]) {
          return item;
        }
      }
      return null;
    });
  }


takePhoto() {
    // Camera.getPicture().then((imageData) => {
    //     this.imageURL = imageData
    // }, (err) => {
    //     console.log(err);
    // });
  }


SetPrice(vehicle: any) {
    this.VehicleId = vehicle.MILEAGE_GUID;
    this.VehicleRate = vehicle.RATE_PER_UNIT;
    this.vehicleCategory = vehicle.CATEGORY;
    console.log(vehicle.MILEAGE_GUID);
    console.log(vehicle.RATE_PER_UNIT);
    console.log(vehicle.CATEGORY);
    console.log(this.VehicleId);
  }


// fileChange(){
//   alert('file change');
//   let fileList: FileList = event.target.files;
//   if(fileList.length > 0) {
//       let file: File = fileList[0];
//       let formData:FormData = new FormData();
//       formData.append('uploadFile', file, file.name);
//       let headers = new Headers();
//       /** No need to include Content-Type in Angular 4 */
//       headers.append('Content-Type', 'multipart/form-data');
//       headers.append('Accept', 'application/json');
//       let options = new RequestOptions({ headers: headers });
//       this.http.post(`${this.apiEndPoint}`, formData, options)
//           .map(res => res.json())
//           .catch(error => Observable.throw(error))
//           .subscribe(
//               data => console.log('success'),
//               error => console.log(error)
//           )
//   }
// }
save(value: any) {
    let tenantGUID = localStorage.getItem('g_TENANT_GUID');
    let month = new Date(value.travel_date).getMonth() + 1;
    let year = new Date(value.travel_date).getFullYear();
    let claimRefGUID;
    let url = Services.getUrl('main_claim_ref', 'filter=(USER_GUID=' + this.userGUID + ')AND(MONTH=' + month + ')AND(YEAR=' + year + ')');
    this.http
      .get(url)
      .map(res => res.json())
      .subscribe(claimRefdata => {
        if (claimRefdata["resource"][0] == null) {
          let claimReqRef: ClaimRefMain_Model = new ClaimRefMain_Model();
          claimReqRef.CLAIM_REF_GUID = UUID.UUID();
          claimReqRef.USER_GUID = this.userGUID;
          claimReqRef.TENANT_GUID = tenantGUID;
          claimReqRef.REF_NO = this.userGUID + '/' + month + '/' + year;
          claimReqRef.MONTH = month;
          claimReqRef.YEAR = year;
          claimReqRef.CREATION_TS = new Date().toISOString();
          claimReqRef.UPDATE_TS = new Date().toISOString();

          this.api.postData('main_claim_ref', claimReqRef.toJson(true)).subscribe((response) => {
            var postClaimRef = response.json();
            claimRefGUID = postClaimRef["resource"][0].CLAIM_REF_GUID;

            let claimReqMainRef: ClaimReqMain_Model = new ClaimReqMain_Model();
            claimReqMainRef.CLAIM_REQUEST_GUID = UUID.UUID();
            claimReqMainRef.TENANT_GUID = tenantGUID;
            claimReqMainRef.CLAIM_REF_GUID = claimRefGUID;
            claimReqMainRef.MILEAGE_GUID = this.VehicleId;
            claimReqMainRef.CLAIM_TYPE_GUID = '58c59b56-289e-31a2-f708-138e81a9c823';
            claimReqMainRef.TRAVEL_DATE = value.travel_date;
            claimReqMainRef.START_TS = value.start_DT;
            claimReqMainRef.END_TS = value.end_DT;
            claimReqMainRef.DESCRIPTION = value.description;
            claimReqMainRef.MILEAGE_AMOUNT = this.Travel_Amount_ngModel
            claimReqMainRef.CLAIM_AMOUNT = this.Travel_Amount_ngModel
            claimReqMainRef.CREATION_TS = new Date().toISOString();
            claimReqMainRef.UPDATE_TS = new Date().toISOString();
            claimReqMainRef.FROM = this.Travel_From_ngModel;
            claimReqMainRef.DESTINATION = this.Travel_Destination_ngModel;
            claimReqMainRef.DISTANCE_KM = this.Travel_Distance_ngModel;
            claimReqMainRef.ASSIGNED_TO = this.assignedTo;
            claimReqMainRef.PROFILE_LEVEL = this.profileLevel;
            claimReqMainRef.PROFILE_JSON = this.profileJSON;
            claimReqMainRef.STATUS = 'Pending';
            claimReqMainRef.STAGE = this.stage;
           // claimReqMainRef.SOC_GUID = this.Travel_SOC_No_ngModel;
           if(this.isCustomer){
            claimReqMainRef.CUSTOMER_GUID = this.Customer_GUID ;
          }
          else{
            claimReqMainRef.SOC_GUID = this.Soc_GUID;
          }
          // claimReqMainRef.CUSTOMER_GUID = this.isCustomer ? this.Customer_GUID : this.Soc_GUID;
          // claimReqMainRef.SOC_GUID = this.isCustomer ? this.Customer_GUID : this.Soc_GUID;

            this.api.postData('main_claim_request', claimReqMainRef.toJson(true)).subscribe((response) => {
              var postClaimMain = response.json();
              this.ClaimRequestMain = postClaimMain["resource"][0].CLAIM_REQUEST_GUID;
              this.MainClaimSaved = true;
              console.table( claimReqMainRef);
              alert('Claim Has Registered.')
            })
          })
        }
        else {
          claimRefGUID = claimRefdata["resource"][0].CLAIM_REF_GUID;

          let claimReqMainRef: ClaimReqMain_Model = new ClaimReqMain_Model();
          claimReqMainRef.CLAIM_REQUEST_GUID = UUID.UUID();
          claimReqMainRef.TENANT_GUID = tenantGUID;
          claimReqMainRef.CLAIM_REF_GUID = claimRefGUID;
          claimReqMainRef.MILEAGE_GUID = this.VehicleId;
          claimReqMainRef.CLAIM_TYPE_GUID = '58c59b56-289e-31a2-f708-138e81a9c823';
          claimReqMainRef.TRAVEL_DATE = value.travel_date;
          claimReqMainRef.START_TS = value.start_DT;
          claimReqMainRef.END_TS = value.end_DT;
          claimReqMainRef.DESCRIPTION = value.description;
          claimReqMainRef.MILEAGE_AMOUNT = this.Travel_Amount_ngModel;
          claimReqMainRef.CLAIM_AMOUNT = this.Travel_Amount_ngModel;
          claimReqMainRef.CREATION_TS = new Date().toISOString();
          claimReqMainRef.UPDATE_TS = new Date().toISOString();
          claimReqMainRef.FROM = this.Travel_From_ngModel;
          claimReqMainRef.DESTINATION = this.Travel_Destination_ngModel;
          claimReqMainRef.ASSIGNED_TO = this.assignedTo;
          claimReqMainRef.DISTANCE_KM = this.Travel_Distance_ngModel;
          claimReqMainRef.PROFILE_LEVEL = this.profileLevel;
          claimReqMainRef.PROFILE_JSON = this.profileJSON;
          claimReqMainRef.STATUS = 'Pending';
          //claimReqMainRef.STATUS = 'Pending';
          claimReqMainRef.STAGE = this.stage;
          //claimReqMainRef.SOC_GUID = this.Travel_SOC_No_ngModel;
          if(this.isCustomer){
            claimReqMainRef.CUSTOMER_GUID = this.Customer_GUID ;
          }
          else{
            claimReqMainRef.SOC_GUID = this.Soc_GUID;
          }
        this.api.postData('main_claim_request', claimReqMainRef.toJson(true)).subscribe((response) => {
            var postClaimMain = response.json();
            this.ClaimRequestMain = postClaimMain["resource"][0].CLAIM_REQUEST_GUID;

            this.MainClaimSaved = true;
            alert('Claim Has Registered.')
          })
        }

      })
  }


sendEmail() {
    let name: string; let email: string
    name = 'shabbeer'; email = 'shabbeer@zen.com.my'
    var queryHeaders = new Headers();
    queryHeaders.append('Content-Type', 'application/json');
    queryHeaders.append('X-Dreamfactory-Session-Token', localStorage.getItem('session_token'));
    queryHeaders.append('X-Dreamfactory-API-Key', constants.DREAMFACTORY_API_KEY);
    let options = new RequestOptions({ headers: queryHeaders });

    let body = {
      "template": "",
      "template_id": 0,
      "to": [
        {
          "name": name,
          "email": email
        }
      ],
      "cc": [
        {
          "name": name,
          "email": email
        }
      ],
      "bcc": [
        {
          "name": name,
          "email": email
        }
      ],
      "subject": "Test",
      "body_text": "",
      "body_html": ' <META name=GENERATOR content="MSHTML 10.00.9200.17606">

Notification

Dear [%Variable: @Employee%]

Your [%Variable: @LeaveType%] application has been forwarded to your superior for approval.

Leave Details :

 EMPLOYEE	 :	 [%Variable: @Employee%]

 START DATE	 :	 [%Variable: @StartDate%]

 END DATE 	 :	 [%Variable: @EndDate%]

 APPLIED DATE	 :	 [%Variable: @AppliedDate%]

 DAYS	 :	 [%Variable: @NoOfDays%] 	 [%Variable: @HalfDay%]

 LEAVE TYPE	 :	 [%Variable: @LeaveType%]

 REASON	 : 	 [%Current Item:Reason%]

Thank you.

',
      "from_name": "Ajay DAV",
      "from_email": "ajay1591ani@gmail.com",
      "reply_to_name": "",
      "reply_to_email": ""
    };
    this.http.post(this.emailUrl, body, options)
      .map(res => res.json())
      .subscribe(data => {
        // this.result= data["resource"];
        alert(JSON.stringify(data));
      });
  }


showAddToll() {
    //let AddTollModal = this.modalCtrl.create(AddTollPage);
    //AddTollModal.present;
    this.navCtrl.push(AddTollPage, {
      DetailsType: 'Toll',
      MainClaim: this.ClaimRequestMain,
      ClaimMethod: '03048acb-037a-11e8-a50c-00155de7e742'
    });
  }


showAddParking() {
    this.navCtrl.push(AddTollPage, {
      DetailsType: 'Parking',
      MainClaim: this.ClaimRequestMain,
      ClaimMethod: '0ebb7e5f-037a-11e8-a50c-00155de7e742'
    });
  }


readProfile() {
    return this.http.get('assets/profile.json').map((response) => response.json()).subscribe(data => {
      this.profileJSON = JSON.stringify(data);
      //levels: any[];
       let levels: any[] = data.profile.levels.level
      console.table(levels)
      levels.forEach(element => {
        if (element['-id'] == '1') {
          this.profileLevel = '1';
          if (element['approver']['-directManager'] === '1') {
            this.http
              .get(Services.getUrl('user_info', 'filter=USER_GUID=' + this.userGUID))
              .map(res => res.json())
              .subscribe(data => {
                let userInfo: any[] = data["resource"]
                userInfo.forEach(userElm => {
                  this.assignedTo = userElm.MANAGER_USER_GUID
                  this.http
                    .get(Services.getUrl('user_info', 'filter=USER_GUID=' + userElm.MANAGER_USER_GUID))
                    .map(res => res.json())
                    .subscribe(data => {
                      let userInfo: any[] = data["resource"]
                      userInfo.forEach(approverElm => {
                        this.stage = approverElm.DEPT_GUID
                      });
                    });
                });
                // console.log('Direct Manager Exists')
              });
            // console.log('Direct Manager ' + element['approver']['-directManager'])
            let varf: any[]= element['conditions']['condition']
            varf.forEach(condElement => {
              if (condElement['-status'] === 'approved') {
                console.log('Next Level ' + condElement['nextlevel']['#text'])
              }
              console.log('Status ' + condElement['-status'])
            });
          }
          else {
            this.assignedTo = element['approver']['#text']
            this.http
              .get(Services.getUrl('user_info', 'filter=USER_GUID=' + this.assignedTo))
              .map(res => res.json())
              .subscribe(data => {
                let userInfo: any[] = data["resource"]
                userInfo.forEach(approverElm => {
                  this.stage = approverElm.DEPT_GUID
                });
              });

          }
        }
      });
    });
  }


() => {
  beforeAll(() => {
    (window as any)['crypto'] = window.crypto || { getRandomValues };
  });

  it(
    'should create an array of random 4-byte numbers by default',
    observe(() => {
      return fromRandomCrypto().pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      );
    }),
  );

  it(
    'should create an array of random 1-byte integer',
    observe(() =>
      fromRandomCrypto(0, { bytes: 1 }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      ),
    ),
  );

  it(
    'should create an array of random 1-byte unsigned integer',
    observe(() =>
      fromRandomCrypto(0, { bytes: 1, unsigned: true }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      ),
    ),
  );

  it(
    'should create an array of random 2-byte integer',
    observe(() =>
      fromRandomCrypto(0, { bytes: 2 }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      ),
    ),
  );

  it(
    'should create an array of random 2-byte unsigned integer',
    observe(() =>
      fromRandomCrypto(0, { bytes: 2, unsigned: true }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      ),
    ),
  );

  it(
    'should create an array of random 4-byte integer',
    observe(() =>
      fromRandomCrypto(0, { bytes: 4 }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      ),
    ),
  );

  it(
    'should create an array of random 4-byte unsigned integer',
    observe(() =>
      fromRandomCrypto(0, { bytes: 4, unsigned: true }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      ),
    ),
  );
}


() => {
    (window as any)['crypto'] = window.crypto || { getRandomValues };
  }


() => {
      return fromRandomCrypto().pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      );
    }


(acc, val) => [...acc, val]


(value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }


(v) => typeof v === 'number' && Number.isInteger(v)


() =>
      fromRandomCrypto(0, { bytes: 1 }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      )


() =>
      fromRandomCrypto(0, { bytes: 1, unsigned: true }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      )


() =>
      fromRandomCrypto(0, { bytes: 2 }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      )


() =>
      fromRandomCrypto(0, { bytes: 2, unsigned: true }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      )


() =>
      fromRandomCrypto(0, { bytes: 4 }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      )


() =>
      fromRandomCrypto(0, { bytes: 4, unsigned: true }).pipe(
        take(5),
        reduce<number, number[]>((acc, val) => [...acc, val], []),
        tap((value) => {
          const check = value.filter((v) => typeof v === 'number' && Number.isInteger(v));
          expect(check.length).toBe(5);
        }),
      )


() => {
  let page: AppPage;
  let map: Map;

  beforeEach(() => {
    page = new AppPage();
    map = new Map();
    page.navigateTo();
    browser.driver.manage().window().maximize();
  });

  it('should open a dropdown on click', () => {
    const dropdown = map.selectElement();
    dropdown.click();
    expect(element(by.css('ul.dropdown-menu')).isPresent()).toBeTruthy();
  });

  it('should display the slider on selecting a layer', () => {
    expect(map.sliderElement().isPresent()).toBeFalsy();
    const dropdown = map.selectElement();
    dropdown.click();
    element(by.css('ul.dropdown-menu li:nth-child(3)')).click();
    expect(map.sliderElement().isPresent()).toBeTruthy();
  });
}


() => {
    page = new AppPage();
    map = new Map();
    page.navigateTo();
    browser.driver.manage().window().maximize();
  }


() => {
    const dropdown = map.selectElement();
    dropdown.click();
    expect(element(by.css('ul.dropdown-menu')).isPresent()).toBeTruthy();
  }


() => {
    expect(map.sliderElement().isPresent()).toBeFalsy();
    const dropdown = map.selectElement();
    dropdown.click();
    element(by.css('ul.dropdown-menu li:nth-child(3)')).click();
    expect(map.sliderElement().isPresent()).toBeTruthy();
  }


({ isVisible, children }: Props) => (
  <AnimatedContainer
    animate={isVisible ? 'visible' : 'hidden'}


type Props = {
  isVisible: boolean
  children: ReactNode
}


export default function useVModel<T, P extends (...args: any) => void>(
  value: Ref<T>,
  modelValue: Ref<T>,
  defaultValue: T,
  onChange: P,
  propName = 'value',
  // emit 和 eventName 用于支持 v-model 和 xxx.sync 语法糖
): [Ref<T>, ChangeHandler<T>] {
  const { emit, attrs } = getCurrentInstance();
  const internalValue: Ref<T> = ref();

  // 非受控模式,defaultValue 只消费一次
  internalValue.value = defaultValue;

  if (typeof value.value !== 'undefined') {
    // 受控模式 v-model:propName
    internalValue.value = value.value;
  } else if (typeof modelValue.value !== 'undefined') {
    // 受控模式:modelValue v-model
    internalValue.value = modelValue.value;
  }

  // 监听value与modelValue的变化
  watch(value, (newVal) => {
    internalValue.value = newVal;
  });
  watch(modelValue, (newVal) => {
    internalValue.value = newVal;
  });

  return [
    internalValue,
    (newValue, ...args) => {
      if (attrs[`onUpdate:${propName}`]) {
        // 受控模式 v-model:propName
        emit?.(`update:${propName}`, newValue, ...args);
      } else if (attrs['onUpdate:modelValue']) {
        // 受控模式:modelValue v-model
        emit?.(`update:modelValue`, newValue, ...args);
      }

      if (typeof value.value === 'undefined' && typeof modelValue.value === 'undefined') {
        internalValue.value = newValue;
      }

      onChange?.(newValue, ...args);
    },
  ];
}


(newVal) => {
    internalValue.value = newVal;
  }


(newValue, ...args) => {
      if (attrs[`onUpdate:${propName}`]) {
        // 受控模式 v-model:propName
        emit?.(`update:${propName}`, newValue, ...args);
      } else if (attrs['onUpdate:modelValue']) {
        // 受控模式:modelValue v-model
        emit?.(`update:modelValue`, newValue, ...args);
      }

      if (typeof value.value === 'undefined' && typeof modelValue.value === 'undefined') {
        internalValue.value = newValue;
      }

      onChange?.(newValue, ...args);
    }


export type ChangeHandler<T> = (value: T, ...args: any) => void;


({ theme }) => theme.application().lowContrast().hex()


resolve => resolve(this.isAuthenticated)


(res) => {
          this.user = res;
        }


error1 => {
          console.log(error1);
        }


(resolve) => {
      if(typeof this.user === "undefined"){
        setTimeout(()=>{
          if(typeof this.user === "undefined"){
            setTimeout(()=>{
              if(!(typeof this.user === "undefined"))
                resolve(this.user.name)
            },100)
          }else
            resolve(this.user.name)
          }, 100);
      }
    }


()=>{
          if(typeof this.user === "undefined"){
            setTimeout(()=>{
              if(!(typeof this.user === "undefined"))
                resolve(this.user.name)
            },100)
          }else
            resolve(this.user.name)
          }


()=>{
              if(!(typeof this.user === "undefined"))
                resolve(this.user.name)
            }


() => {
          this.router.navigate(['/login']);
        }


(error) => {
          console.log(error);
        }


(res) => {}


error1 => console.log("error updating: " + error1)


@Injectable()
export class UserService {
  isAuthenticated = false;
  user: User;
  userNamePromise;
  private REGISTER_API = api_address + user_endpoint + register_endpoint;

  constructor(private httpClient : HttpClient, private router: Router){}

  checkAuthenticated () {
    return new Promise(
      resolve => resolve(this.isAuthenticated));
  }

  fetchUserByUserkey(userkey : string) {
    let address :string = api_address + userByKey_endpoint + userkey;
    console.log(address);
    this.httpClient
      .get<any>(address)
      .subscribe(
        (res) => {
          this.user = res;
        },
        (error1 => {
          console.log(error1);
        }));
  }

  getUserByUserKey(userkey : string){
    let address :string = api_address + userByKey_endpoint + userkey;
    console.log(address);
    return this.httpClient
      .get<User>(address);
  }

  getUserName(){
    return new Promise((resolve) => {
      if(typeof this.user === "undefined"){
        setTimeout(()=>{
          if(typeof this.user === "undefined"){
            setTimeout(()=>{
              if(!(typeof this.user === "undefined"))
                resolve(this.user.name)
            },100)
          }else
            resolve(this.user.name)
          }, 100);
      }
    });
  }

  registerUser(info : RegistrationModel){
    this.httpClient
      .post<LoginModel>(this.REGISTER_API, info)
      .subscribe(
        () => {
          this.router.navigate(['/login']);
        },
        (error) => {
          console.log(error);
        }
      );
  }

  updateUser(updatedUser: User) {
    let address :string = api_address + user_endpoint;
    console.log("updating at: "  + address);
    this.httpClient
      .post<User>(address, updatedUser)
      .subscribe(
        (res) => {},
        (error1 => console.log("error updating: " + error1))
      )
  }
}


checkAuthenticated () {
    return new Promise(
      resolve => resolve(this.isAuthenticated));
  }


fetchUserByUserkey(userkey : string) {
    let address :string = api_address + userByKey_endpoint + userkey;
    console.log(address);
    this.httpClient
      .get<any>(address)
      .subscribe(
        (res) => {
          this.user = res;
        },
        (error1 => {
          console.log(error1);
        }));
  }


getUserByUserKey(userkey : string){
    let address :string = api_address + userByKey_endpoint + userkey;
    console.log(address);
    return this.httpClient
      .get<User>(address);
  }


getUserName(){
    return new Promise((resolve) => {
      if(typeof this.user === "undefined"){
        setTimeout(()=>{
          if(typeof this.user === "undefined"){
            setTimeout(()=>{
              if(!(typeof this.user === "undefined"))
                resolve(this.user.name)
            },100)
          }else
            resolve(this.user.name)
          }, 100);
      }
    });
  }


registerUser(info : RegistrationModel){
    this.httpClient
      .post<LoginModel>(this.REGISTER_API, info)
      .subscribe(
        () => {
          this.router.navigate(['/login']);
        },
        (error) => {
          console.log(error);
        }
      );
  }


updateUser(updatedUser: User) {
    let address :string = api_address + user_endpoint;
    console.log("updating at: "  + address);
    this.httpClient
      .post<User>(address, updatedUser)
      .subscribe(
        (res) => {},
        (error1 => console.log("error updating: " + error1))
      )
  }


export default class {
  config: AiryWidgetConfiguration;

  constructor(config: AiryWidgetConfiguration) {
    this.config = config;
  }

  render(target: HTMLElement) {
    render(<Chat {...this.config} />, target);
  }
}


render(target: HTMLElement) {
    render(<Chat {...this.config} />, target);
  }


p => p.theme.fontFamily.heading


async ({ commit, dispatch, rootState }, { token, type, payload }) => {
    commit('SEND_REQUEST');
    try {
      const msg: any = {
        address: rootState.web3.account,
        msg: JSON.stringify({
          version,
          timestamp: (Date.now() / 1e3).toFixed(),
          token,
          type,
          payload
        })
      };
      msg.sig = await dispatch('signMessage', msg.msg);
      const result = await client.request('message', msg);
      commit('SEND_SUCCESS');
      dispatch('notify', ['green', `Your ${type} is in!`]);
      return result;
    } catch (e) {
      commit('SEND_FAILURE', e);
      const errorMessage =
        e && e.error_description
          ? `Oops, ${e.error_description}`
          : 'Oops, something went wrong!';
      dispatch('notify', ['red', errorMessage]);
      return;
    }
  }


async ({ commit }, space) => {
    commit('GET_PROPOSALS_REQUEST');
    try {
      let proposals: any = await client.request(`${space.address}/proposals`);
      if (proposals) {
        const scores: any = await getScores(
          space.strategies,
          space.chainId,
          getProvider(space.chainId),
          Object.values(proposals).map((proposal: any) => proposal.address)
        );
        proposals = Object.fromEntries(
          Object.entries(proposals).map((proposal: any) => {
            proposal[1].score = scores.reduce(
              (a, b) => a + b[proposal[1].address],
              0
            );
            return [proposal[0], proposal[1]];
          })
        );
      }
      commit('GET_PROPOSALS_SUCCESS');
      return formatProposals(proposals);
    } catch (e) {
      commit('GET_PROPOSALS_FAILURE', e);
    }
  }


(proposal: any) => proposal.address


(proposal: any) => {
            proposal[1].score = scores.reduce(
              (a, b) => a + b[proposal[1].address],
              0
            );
            return [proposal[0], proposal[1]];
          }


(a, b) => a + b[proposal[1].address]


async ({ commit, rootState }, payload) => {
    commit('GET_PROPOSAL_REQUEST');
    try {
      const result: any = {};
      const [proposal, votes] = await Promise.all([
        ipfs.get(payload.id),
        client.request(`${payload.space.address}/proposal/${payload.id}`)
      ]);
      result.proposal = formatProposal(proposal);
      result.proposal.ipfsHash = payload.id;
      result.votes = votes;
      const { snapshot } = result.proposal.msg.payload;
      const blockTag =
        snapshot > rootState.web3.blockNumber ? 'latest' : parseInt(snapshot);
      const scores: any = await getScores(
        payload.space.strategies,
        payload.space.chainId,
        getProvider(payload.space.chainId),
        Object.keys(result.votes),
        // @ts-ignore
        blockTag
      );
      console.log('Scores', scores);
      result.votes = Object.fromEntries(
        Object.entries(result.votes)
          .map((vote: any) => {
            vote[1].scores = payload.space.strategies.map(
              (strategy, i) => scores[i][vote[1].address] || 0
            );
            vote[1].balance = vote[1].scores.reduce((a, b: any) => a + b, 0);
            return vote;
          })
          .sort((a, b) => b[1].balance - a[1].balance)
          .filter(vote => vote[1].balance > 0)
      );
      result.results = {
        totalVotes: result.proposal.msg.payload.choices.map(
          (choice, i) =>
            Object.values(result.votes).filter(
              (vote: any) => vote.msg.payload.choice === i + 1
            ).length
        ),
        totalBalances: result.proposal.msg.payload.choices.map((choice, i) =>
          Object.values(result.votes)
            .filter((vote: any) => vote.msg.payload.choice === i + 1)
            .reduce((a, b: any) => a + b.balance, 0)
        ),
        totalScores: result.proposal.msg.payload.choices.map((choice, i) =>
          payload.space.strategies.map((strategy, sI) =>
            Object.values(result.votes)
              .filter((vote: any) => vote.msg.payload.choice === i + 1)
              .reduce((a, b: any) => a + b.scores[sI], 0)
          )
        ),
        totalVotesBalances: Object.values(result.votes).reduce(
          (a, b: any) => a + b.balance,
          0
        )
      };
      commit('GET_PROPOSAL_SUCCESS');
      return result;
    } catch (e) {
      commit('GET_PROPOSAL_FAILURE', e);
    }
  }


(vote: any) => {
            vote[1].scores = payload.space.strategies.map(
              (strategy, i) => scores[i][vote[1].address] || 0
            );
            vote[1].balance = vote[1].scores.reduce((a, b: any) => a + b, 0);
            return vote;
          }


(strategy, i) => scores[i][vote[1].address] || 0


(a, b: any) => a + b


(a, b) => b[1].balance - a[1].balance


vote => vote[1].balance > 0


(choice, i) =>
            Object.values(result.votes).filter(
              (vote: any) => vote.msg.payload.choice === i + 1
            ).length


(vote: any) => vote.msg.payload.choice === i + 1


(choice, i) =>
          Object.values(result.votes)
            .filter((vote: any) => vote.msg.payload.choice === i + 1)
            .reduce((a, b: any) => a + b.balance, 0)


(a, b: any) => a + b.balance


(choice, i) =>
          payload.space.strategies.map((strategy, sI) =>
            Object.values(result.votes)
              .filter((vote: any) => vote.msg.payload.choice === i + 1)
              .reduce((a, b: any) => a + b.scores[sI], 0)
          )


(strategy, sI) =>
            Object.values(result.votes)
              .filter((vote: any) => vote.msg.payload.choice === i + 1)
              .reduce((a, b: any) => a + b.scores[sI], 0)


(a, b: any) => a + b.scores[sI]


async ({ commit, rootState }, { space, address, snapshot }) => {
    commit('GET_POWER_REQUEST');
    try {
      const blockTag =
        snapshot > rootState.web3.blockNumber ? 'latest' : parseInt(snapshot);
      let scores: any = await getScores(
        space.strategies,
        space.chainId,
        getProvider(space.chainId),
        [address],
        // @ts-ignore
        blockTag
      );
      scores = scores.map((score: any) =>
        Object.values(score).reduce((a, b: any) => a + b, 0)
      );
      commit('GET_POWER_SUCCESS');
      return {
        scores,
        totalScore: scores.reduce((a, b: any) => a + b, 0)
      };
    } catch (e) {
      commit('GET_POWER_FAILURE', e);
    }
  }


(score: any) =>
        Object.values(score).reduce((a, b: any) => a + b, 0)


SEND_REQUEST() {
    console.debug('SEND_REQUEST');
  }


SEND_SUCCESS() {
    console.debug('SEND_SUCCESS');
  }


SEND_FAILURE(_state, payload) {
    console.debug('SEND_FAILURE', payload);
  }


GET_PROPOSALS_REQUEST() {
    console.debug('GET_PROPOSALS_REQUEST');
  }


GET_PROPOSALS_SUCCESS() {
    console.debug('GET_PROPOSALS_SUCCESS');
  }


GET_PROPOSALS_FAILURE(_state, payload) {
    console.debug('GET_PROPOSALS_FAILURE', payload);
  }


GET_PROPOSAL_REQUEST() {
    console.debug('GET_PROPOSAL_REQUEST');
  }


GET_PROPOSAL_SUCCESS() {
    console.debug('GET_PROPOSAL_SUCCESS');
  }


GET_PROPOSAL_FAILURE(_state, payload) {
    console.debug('GET_PROPOSAL_FAILURE', payload);
  }


GET_POWER_REQUEST() {
    console.debug('GET_POWER_REQUEST');
  }


GET_POWER_SUCCESS() {
    console.debug('GET_POWER_SUCCESS');
  }


GET_POWER_FAILURE(_state, payload) {
    console.debug('GET_POWER_FAILURE', payload);
  }


() => {
  ngMocks.faster(); // the same TestBed for several its.

  // Because we want to test the pipe, we pass it as the first
  // parameter of MockBuilder. We can omit the second parameter,
  // because there are no dependencies.
  // Do not forget to return the promise of MockBuilder.
  beforeEach(() => MockBuilder(TargetPipe));

  it('sorts strings', () => {
    const fixture = MockRender(`{{ values | target}}`, {
      values: ['1', '3', '2'],
    });

    expect(fixture.nativeElement.innerHTML).toEqual('1, 2, 3');
  });

  it('reverses strings on param', () => {
    const fixture = MockRender(`{{ values | target:flag}}`, {
      flag: false,
      values: ['1', '3', '2'],
    });

    expect(fixture.nativeElement.innerHTML).toEqual('3, 2, 1');
  });
}


() => MockBuilder(TargetPipe)


() => {
    const fixture = MockRender(`{{ values | target}}`, {
      values: ['1', '3', '2'],
    });

    expect(fixture.nativeElement.innerHTML).toEqual('1, 2, 3');
  }


() => {
    const fixture = MockRender(`{{ values | target:flag}}`, {
      flag: false,
      values: ['1', '3', '2'],
    });

    expect(fixture.nativeElement.innerHTML).toEqual('3, 2, 1');
  }


// A simple pipe that accepts an array of strings, sorts them,
// and returns a joined string of the values.
@Pipe({
  name: 'target',
})
class TargetPipe implements PipeTransform {
  public transform(value: string[], asc = true): string {
    let result = [...(value || [])].sort();
    if (!asc) {
      result = result.reverse();
    }

    return result.join(', ');
  }
}


public transform(value: string[], asc = true): string {
    let result = [...(value || [])].sort();
    if (!asc) {
      result = result.reverse();
    }

    return result.join(', ');
  }


(props) => {
  const router = useRouter()

  return (
    <Card className={styles.card} hoverable>
      <div
        className={styles.cardBody} onClick={()


(match, offset) => `<font color="#7af87a">${word.substr(offset, match.length)}</font>`


g => `<strong${g[1] === '1' ? '' : ' style="color: #DBA"'}>${Utils.escapeHTML(g[0])}</strong>`


w => w.length > 20


room => ({
	label: "Hangman",
	permission: 'editroom',
	options: [
		[`disabled`, room.settings.hangmanDisabled || 'hangman disable'],
		[`enabled`, !room.settings.hangmanDisabled || 'hangman enable'],
	],
})


choose(user: User, word: string) {
		if (user.id === this.creator) throw new Chat.ErrorMessage("You can't guess in your own hangman game.");

		const sanitized = word.replace(/[^A-Za-z ]/g, '');
		const normalized = toID(sanitized);
		if (normalized.length < 1) {
			throw new Chat.ErrorMessage(`Use "/guess [letter]" to guess a letter, or "/guess [phrase]" to guess the entire Hangman phrase.`);
		}
		if (sanitized.length > 30) throw new Chat.ErrorMessage(`Guesses must be 30 or fewer letters – "${word}" is too long.`);

		for (const guessid of this.guesses) {
			if (normalized === toID(guessid)) throw new Chat.ErrorMessage(`Your guess "${word}" has already been guessed.`);
		}

		if (sanitized.length > 1) {
			if (!this.guessWord(sanitized, user.name)) {
				throw new Chat.ErrorMessage(`Your guess "${sanitized}" is invalid.`);
			} else {
				this.room.send(`${user.name} guessed "${sanitized}"!`);
			}
		} else {
			if (!this.guessLetter(sanitized, user.name)) {
				throw new Chat.ErrorMessage(`Your guess "${sanitized}" is not a valid letter.`);
			}
		}
	}


guessLetter(letter: string, guesser: string) {
		letter = letter.toUpperCase();
		if (this.guesses.includes(letter)) return false;
		if (this.word.toUpperCase().includes(letter)) {
			for (let i = 0; i < this.word.length; i++) {
				if (this.word[i].toUpperCase() === letter) {
					this.wordSoFar[i] = this.word[i];
				}
			}

			if (!this.wordSoFar.includes('_')) {
				this.incorrectGuesses = -1;
				this.guesses.push(letter);
				this.letterGuesses.push(`${letter}1`);
				this.lastGuesser = guesser;
				this.finish();
				return true;
			}
			this.letterGuesses.push(`${letter}1`);
		} else {
			this.incorrectGuesses++;
			this.letterGuesses.push(`${letter}0`);
		}

		this.guesses.push(letter);
		this.lastGuesser = guesser;
		this.update();
		return true;
	}


guessWord(word: string, guesser: string) {
		const ourWord = toID(this.word);
		const guessedWord = toID(word);
		if (ourWord === guessedWord) {
			for (const [i, letter] of this.wordSoFar.entries()) {
				if (letter === '_') {
					this.wordSoFar[i] = this.word[i];
				}
			}
			this.incorrectGuesses = -1;
			this.guesses.push(word);
			this.lastGuesser = guesser;
			this.finish();
			return true;
		} else if (ourWord.length === guessedWord.length) {
			this.incorrectGuesses++;
			this.guesses.push(word);
			this.lastGuesser = guesser;
			this.update();
			return true;
		}
		return false;
	}


hangingMan() {
		return `<img width="120" height="120" src="//${Config.routes.client}/fx/hangman${this.incorrectGuesses === -1 ? 7 : this.incorrectGuesses}.png" />`;
	}


generateWindow() {
		let result = 0;

		if (this.incorrectGuesses === maxMistakes) {
			result = 1;
		} else if (!this.wordSoFar.includes('_')) {
			result = 2;
		}

		const color = result === 1 ? 'red' : (result === 2 ? 'green' : 'blue');
		const message = `${result === 1 ? 'Too bad! The mon has been hanged.' : (result === 2 ? 'The word has been guessed. Congratulations!' : 'Hangman')}`;
		let output = `<div class="broadcast-${color}">`;
		output += `<p style="text-align:left;font-weight:bold;font-size:10pt;margin:5px 0 0 15px">${message}</p>`;
		output += `<table><tr><td style="text-align:center;">${this.hangingMan()}</td><td style="text-align:center;width:100%;word-wrap:break-word">`;

		let wordString = this.wordSoFar.join('');
		if (result === 1) {
			const word = this.word;
			wordString = wordString.replace(
				/_+/g,
				(match, offset) => `<font color="#7af87a">${word.substr(offset, match.length)}</font>`
			);
		}

		if (this.hint) output += Utils.html`<div>(Hint: ${this.hint})</div>`;
		output += `<p style="font-weight:bold;font-size:12pt;letter-spacing:3pt">${wordString}</p>`;
		if (this.guesses.length) {
			if (this.letterGuesses.length) {
				output += 'Letters: ' + this.letterGuesses.map(
					g => `<strong${g[1] === '1' ? '' : ' style="color: #DBA"'}>${Utils.escapeHTML(g[0])}</strong>`
				).join(', ');
			}
			if (result === 2) {
				output += Utils.html`<br />Winner: ${this.lastGuesser}`;
			} else if (this.guesses[this.guesses.length - 1].length === 1) {
				// last guess was a letter
				output += Utils.html` <small>– ${this.lastGuesser}</small>`;
			} else {
				output += Utils.html`<br />Guessed: ${this.guesses[this.guesses.length - 1]} ` +
					`<small>– ${this.lastGuesser}</small>`;
			}
		}

		output += '

';

		return output;
	}


display(user: User, broadcast = false) {
		if (broadcast) {
			this.room.add(`|uhtml|hangman${this.gameNumber}|${this.generateWindow()}`);
		} else {
			user.sendTo(this.room, `|uhtml|hangman${this.gameNumber}|${this.generateWindow()}`);
		}
	}


update() {
		this.room.uhtmlchange(`hangman${this.gameNumber}`, this.generateWindow());

		if (this.incorrectGuesses === maxMistakes) {
			this.finish();
		}
	}


end() {
		this.room.uhtmlchange(`hangman${this.gameNumber}`, '
(The game of hangman was ended.)
');
		this.room.add("The game of hangman was ended.");
		this.room.game = null;
	}


finish() {
		this.room.uhtmlchange(`hangman${this.gameNumber}`, '
(The game of hangman has ended – scroll down to see the results)
');
		this.room.add(`|html|${this.generateWindow()}`);
		this.room.game = null;
	}


new(target, room, user, connection) {
			room = this.requireRoom();
			const text = this.filter(target);
			if (target !== text) return this.errorReply("You are not allowed to use filtered words in hangmans.");
			const params = text.split(',');

			this.checkCan('minigame', null, room);
			if (room.settings.hangmanDisabled) return this.errorReply("Hangman is disabled for this room.");
			this.checkChat();
			if (room.game) return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`);

			if (!params) return this.errorReply("No word entered.");
			const word = params[0].replace(/[^A-Za-z '-]/g, '');
			if (word.replace(/ /g, '').length < 1) return this.errorReply("Enter a valid word");
			if (word.length > 30) return this.errorReply("Phrase must be less than 30 characters.");
			if (word.split(' ').some(w => w.length > 20)) {
				return this.errorReply("Each word in the phrase must be less than 20 characters.");
			}
			if (!/[a-zA-Z]/.test(word)) return this.errorReply("Word must contain at least one letter.");

			let hint;
			if (params.length > 1) {
				hint = params.slice(1).join(',').trim();
				if (hint.length > 150) return this.errorReply("Hint too long.");
			}

			const game = new Hangman(room, user, word, hint);
			room.game = game;
			game.display(user, true);

			this.modlog('HANGMAN');
			return this.addModAction(`A game of hangman was started by ${user.name} – use /guess to play!`);
		}


guess(target, room, user) {
			this.parse(`/choose ${target}`);
		}


end(target, room, user) {
			room = this.requireRoom();
			this.checkCan('minigame', null, room);
			this.checkChat();
			const game = this.requireGame(Hangman);
			game.end();
			this.modlog('ENDHANGMAN');
			return this.privateModAction(`The game of hangman was ended by ${user.name}.`);
		}


disable(target, room, user) {
			room = this.requireRoom();
			this.checkCan('gamemanagement', null, room);
			if (room.settings.hangmanDisabled) {
				return this.errorReply("Hangman is already disabled.");
			}
			room.settings.hangmanDisabled = true;
			room.saveSettings();
			return this.sendReply("Hangman has been disabled for this room.");
		}


enable(target, room, user) {
			room = this.requireRoom();
			this.checkCan('gamemanagement', null, room);
			if (!room.settings.hangmanDisabled) {
				return this.errorReply("Hangman is already enabled.");
			}
			delete room.settings.hangmanDisabled;
			room.saveSettings();
			return this.sendReply("Hangman has been enabled for this room.");
		}


display(target, room, user) {
			room = this.requireRoom();
			const game = this.requireGame(Hangman);
			if (!this.runBroadcast()) return;
			room.update();

			game.display(user, this.broadcasting);
		}


''(target, room, user) {
			return this.parse('/help hangman');
		}


async function main() {
  const [dir, all, id] = cliArguments();

  const { img, num: length } = await xkdc(id);

  await ensureDir(dir);

  const kia = spinner(
    `Downloading ${all ? `${length} ` : ""}comic${all ? "s" : ""}...`,
  );

  kia.start();

  if (all) {
    const dp = Array.from({ length });

    for (let index = 0; index < length; index++) {
      if (index == 404) continue; // little xkcd author bad joke
      const { img, num } = await xkdc(index);
      dp.push(download(img, `${dir}/${num}_${filenameFromURL(img)}`));
    }

    await Promise.all(dp);
  } else {
    await download(img, `${dir}/${length}_${filenameFromURL(img)}`);
  }

  kia.succeed();
}


(): ReactElement => (


data => {
      if (data.isSuccessfully) {
        res.status(HttpStatus.OK).end(data.message);
      }
      res.status(HttpStatus.BAD_REQUEST).end(data.message);
    }


isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Successfully');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    }


data => {
      if (data) {
        res.status(HttpStatus.MOVED_PERMANENTLY).end('https://myside/callback?access_token=' + data);
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    }


isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Reset password is sent to your email');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    }


@Controller('user')
export class UserController {
  constructor(private readonly _userService: UserService) {}

  @Get('')
  showLoginPage() {
    return 'login page';
  }

  @Post('register')
  async register(@Body() body, @Res() res) {
    this._userService.signUp(body).then(data => {
      if (data.isSuccessfully) {
        res.status(HttpStatus.OK).end(data.message);
      }
      res.status(HttpStatus.BAD_REQUEST).end(data.message);
    });
  }

  @Post('change-password')
  async changePassword(@Body() body, @Res() res) {
    await this._userService.changePassword(body).then(isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Successfully');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }

  @Post('delete')
  async delete(@Body() body: Login,  @Res() res) {
    await this._userService.delete(body).then(isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Successfully');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }

  @Post('login')
  async login(@Body() body: Login,  @Query() client: ClientInfo, @Res() res) {
    await this._userService.login(body, client).then(data => {
      if (data) {
        res.status(HttpStatus.MOVED_PERMANENTLY).end('https://myside/callback?access_token=' + data);
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }

  @Post('reset-password')
  async resetPassword(@Body() body: any, @Res() res) {
    await this._userService.resetPassword(body.email).then(isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Reset password is sent to your email');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }
}


@Get('')
  showLoginPage() {
    return 'login page';
  }


@Post('register')
  async register(@Body() body, @Res() res) {
    this._userService.signUp(body).then(data => {
      if (data.isSuccessfully) {
        res.status(HttpStatus.OK).end(data.message);
      }
      res.status(HttpStatus.BAD_REQUEST).end(data.message);
    });
  }


@Post('change-password')
  async changePassword(@Body() body, @Res() res) {
    await this._userService.changePassword(body).then(isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Successfully');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }


@Post('delete')
  async delete(@Body() body: Login,  @Res() res) {
    await this._userService.delete(body).then(isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Successfully');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }


@Post('login')
  async login(@Body() body: Login,  @Query() client: ClientInfo, @Res() res) {
    await this._userService.login(body, client).then(data => {
      if (data) {
        res.status(HttpStatus.MOVED_PERMANENTLY).end('https://myside/callback?access_token=' + data);
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }


@Post('reset-password')
  async resetPassword(@Body() body: any, @Res() res) {
    await this._userService.resetPassword(body.email).then(isSuccess => {
      if (isSuccess) {
        res.status(HttpStatus.OK).end('Reset password is sent to your email');
      }
      res.status(HttpStatus.BAD_REQUEST).end('Failed');
    });
  }


export interface Transformer {
  /** 正向的转换函数 */
  from?: (schema: any) => any;
  /** 反向的转换函数 */
  to?: (schema: any) => any;
  /** 反向的转换函数 */
  fromSetting?: (schema: any) => any;
  /** 反向的转换函数 */
  toSetting?: (schema: any) => any;
}


export interface ExtraButton {
  /** 按钮文案 */
  text: string;
  /** 点击回调 */
  onClick?: (event: any) => void;
  [key: string]: any;
}


export interface ControlButton {
  /** 按钮文案 */
  text?: string;
  /** 点击回调 */
  onClick?: (event: any, schema: any) => void;
}


export interface SettingWidget {
  /** 按钮生成的 schema 的 key 值 */
  name: string;
  /** 在左侧栏按钮展示文案 */
  text: string;
  /** 在左侧栏按钮展示图标 */
  icon?: string | ReactNode;
  /** 如果是基本组件，这个字段注明它对应的 widgets */
  widget?: string;
  /** 组件对应的 schema 片段 */
  schema?: any;
  /** 组件的配置信息，使用 form-render 的 schema 来描述 */
  setting?: any;
}


export interface Setting {
  /** 最外层的分组名称 */
  title: string;
  /** 每个组件的配置，在左侧栏是一个按钮 */
  widgets: SettingWidget[];
  show?: boolean;
  useCommon?: boolean;
}


export interface FRGeneratorProps {
  getId?: (name: string) => string;
  /** 隐藏组件 ID */
  hideId?: boolean;
  /** 固定 id */
  fixedName?: boolean;
  /** 组件删除控制 */
  canDelete?: boolean | Function;
  /** 默认一进入编辑器展示的表单对应的 schema */
  defaultValue?: any;
  /** 自定义 schema 到 form-render 的 schema 的双向转换函数 */
  transformer?: Transformer;
  /** 编辑区顶部的自定义按钮 */
  extraButtons?: (ExtraButton | boolean | string)[];
  /** 选中项操作按钮 */
  controlButtons?: (ControlButton | boolean | Function)[];
  /** 左右侧栏配置 */
  settings?: Setting[];
  /** 通用配置 */
  commonSettings?: any;
  /** 全局配置 */
  globalSettings?: any;
  /** 自定义组件 */
  widgets?: any;
  /** 配置栏自定义组件 */
  settingsWidgets?: any;
  /** 组件和 schema 的映射规则 */
  mapping?: any;
  /** 配置表单校验 */
  validation?: boolean;
  fieldRender?: (
    schema?: any,
    widgetProps?: any,
    children?: ReactNode,
    originNode?: ReactNode
  ) => ReactNode;
  fieldWrapperRender?: (
    schema?: any,
    isSelected?: boolean,
    children?: ReactNode,
    originNode?: ReactNode
  ) => ReactNode;
  elementRender?: (
    schema?: any,
    widgetProps?: SettingWidget,
    originNode?: ReactNode
  ) => ReactNode;
  /** 表单 data 变化回调 */
  onChange?: (data: any) => void;
  /** 表单 schema 变化回调 */
  onSchemaChange?: (schema: any) => void;
  /** 画布组件选择回调 */
  onCanvasSelect?: (schema: any) => void;
}


export interface SettingsProps {
  /** 自定义组件 */
  widgets?: any;
}


export interface CanvasProps {
  /** 画布组件选择回调 */
  onCanvasSelect?: (schema: any) => void;
}


export interface SidebarProps {
  /** 固定 id */
  fixedName?: boolean;
}


export interface Generator
  extends ForwardRefExoticComponent<
    FRGeneratorProps & RefAttributes<HTMLElement>
  > {
  Provider: FC<FRGeneratorProps>;
  Settings: FC<SettingsProps>;
  Canvas: FC<CanvasProps>;
  Sidebar: FC<SidebarProps>;
}


@Component({
  selector: 'home',
  templateUrl: './components/home/home.html',
  styleUrls: ['./components/home/home.css'],
  directives: [ExpensesListComponent, ExpenseFormComponent]
})
export class HomeCmp {
  expenseService:ExpenseService;

  constructor(expenseService:ExpenseService) {
    this.expenseService = expenseService;
  }
}


function createArrayFormWith2Items() {
            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Array'),
                    partitioning: 'invariant',
                    nested: [
                        createNestedField({ id: 41, properties: createProperties('String') }),
                        createNestedField({ id: 42, properties: createProperties('String', { defaultValue: 'Default' }), isDisabled: true }),
                    ],
                }),
            ]);

            const array = contentForm.get('field4')!.get('iv') as FieldArrayForm;

            contentForm.load({
                field4: {
                    iv: [{
                        nested41: 'Text1',
                    }, {
                        nested41: 'Text2',
                    }],
                },
            });

            return { contentForm, array };
        }


function expectLength(array: FieldArrayForm, length: number) {
            expect(array.form.controls.length).toBe(length);
            expect(array.items.length).toBe(length);
        }


function expectForm(parent: AbstractControl, path: string, test: { invalid?: boolean; disabled?: boolean; value?: any }) {
            const form = parent.get(path);

            if (form) {
                for (const key in test) {
                    if (test.hasOwnProperty(key)) {
                        const a = form[key];
                        const e = test[key];

                        expect(a).toBe(e, `Expected ${key} of ${path} to be <${e}>, but found <${a}>.`);
                    }
                }
            } else {
                expect(form).not.toBeNull(`Expected to find form ${path}, but form not found.`);
            }
        }


function createForm(fields: RootFieldDto[], fieldRules: FieldRule[] = [], schemas: { [id: string]: SchemaDto } = {}) {
        return new EditContentForm(languages,
            createSchema({ fields, fieldRules }), schemas, {}, 0);
    }


() => {
    const language = new LanguageDto('en', 'English');
    const fieldInvariant = createField({ properties: createProperties('Number'), partitioning: 'invariant' });
    const fieldLocalized = createField({ properties: createProperties('Number') });
    const fieldAssets = createField({ properties: createProperties('Assets') });

    it('should resolve image url and filename from referenced asset', () => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['url/to/13', 'file13'],
                },
            },
        };

        const assetWithImageAndFileName = createField({ properties: createProperties('Assets', { previewMode: 'ImageAndFileName' }) });

        const result = getContentValue(content, language, assetWithImageAndFileName);

        expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue(' file13') });
    });

    it('should resolve image url only from referenced asset', () => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['url/to/13', 'file13'],
                },
            },
        };

        const assetWithImage = createField({ properties: createProperties('Assets', { previewMode: 'Image' }) });

        const result = getContentValue(content, language, assetWithImage);

        expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue('') });
    });

    it('should resolve filename only from referenced asset', () => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['url/to/13', 'file13'],
                },
            },
        };

        const assetWithFileName = createField({ properties: createProperties('Assets', { previewMode: 'FileName' }) });

        const result = getContentValue(content, language, assetWithFileName);

        expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: 'file13' });
    });

    it('should resolve filename from referenced asset', () => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['file13'],
                },
            },
        };

        const result = getContentValue(content, language, fieldAssets);

        expect(result).toEqual({ value: ['file13'], formatted: 'file13' });
    });

    it('should not image url if not found', () => {
        const content: any = {
            referenceData: {
                field1: {
                    en: null,
                },
            },
        };

        const result = getContentValue(content, language, fieldAssets);

        expect(result).toEqual({ value: '-', formatted: '-' });
    });

    it('should resolve string field from references value', () => {
        const content: any = {
            referenceData: {
                field1: {
                    iv: '13',
                },
            },
        };

        const result = getContentValue(content, language, fieldInvariant);

        expect(result).toEqual({ value: '13', formatted: '13' });
    });

    it('should resolve invariant field from references value', () => {
        const content: any = {
            referenceData: {
                field1: {
                    iv: {
                        en: '13',
                    },
                },
            },
        };

        const result = getContentValue(content, language, fieldInvariant);

        expect(result).toEqual({ value: '13', formatted: '13' });
    });

    it('should resolve localized field from references value', () => {
        const content: any = {
            referenceData: {
                field1: {
                    en: {
                        en: '13',
                    },
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: '13', formatted: '13' });
    });

    it('should return default value if reference field not found', () => {
        const content: any = {
            referenceData: {
                field1: {
                    iv: {
                        en: '13',
                    },
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: '-', formatted: '-' });
    });

    it('should resolve invariant field', () => {
        const content: any = {
            data: {
                field1: {
                    iv: 13,
                },
            },
        };

        const result = getContentValue(content, language, fieldInvariant);

        expect(result).toEqual({ value: 13, formatted: '13' });
    });

    it('should resolve localized field', () => {
        const content: any = {
            data: {
                field1: {
                    en: 13,
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: 13, formatted: '13' });
    });

    it('should return default values if field not found', () => {
        const content: any = {
            data: {
                other: {
                    en: 13,
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: undefined, formatted: '' });
    });
}


() => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['url/to/13', 'file13'],
                },
            },
        };

        const assetWithImageAndFileName = createField({ properties: createProperties('Assets', { previewMode: 'ImageAndFileName' }) });

        const result = getContentValue(content, language, assetWithImageAndFileName);

        expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue(' file13') });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['url/to/13', 'file13'],
                },
            },
        };

        const assetWithImage = createField({ properties: createProperties('Assets', { previewMode: 'Image' }) });

        const result = getContentValue(content, language, assetWithImage);

        expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: new HtmlValue('<img src="url/to/13?width=50&height=50" />') });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['url/to/13', 'file13'],
                },
            },
        };

        const assetWithFileName = createField({ properties: createProperties('Assets', { previewMode: 'FileName' }) });

        const result = getContentValue(content, language, assetWithFileName);

        expect(result).toEqual({ value: ['url/to/13', 'file13'], formatted: 'file13' });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    en: ['file13'],
                },
            },
        };

        const result = getContentValue(content, language, fieldAssets);

        expect(result).toEqual({ value: ['file13'], formatted: 'file13' });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    en: null,
                },
            },
        };

        const result = getContentValue(content, language, fieldAssets);

        expect(result).toEqual({ value: '-', formatted: '-' });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    iv: '13',
                },
            },
        };

        const result = getContentValue(content, language, fieldInvariant);

        expect(result).toEqual({ value: '13', formatted: '13' });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    iv: {
                        en: '13',
                    },
                },
            },
        };

        const result = getContentValue(content, language, fieldInvariant);

        expect(result).toEqual({ value: '13', formatted: '13' });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    en: {
                        en: '13',
                    },
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: '13', formatted: '13' });
    }


() => {
        const content: any = {
            referenceData: {
                field1: {
                    iv: {
                        en: '13',
                    },
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: '-', formatted: '-' });
    }


() => {
        const content: any = {
            data: {
                field1: {
                    iv: 13,
                },
            },
        };

        const result = getContentValue(content, language, fieldInvariant);

        expect(result).toEqual({ value: 13, formatted: '13' });
    }


() => {
        const content: any = {
            data: {
                field1: {
                    en: 13,
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: 13, formatted: '13' });
    }


() => {
        const content: any = {
            data: {
                other: {
                    en: 13,
                },
            },
        };

        const result = getContentValue(content, language, fieldLocalized);

        expect(result).toEqual({ value: undefined, formatted: '' });
    }


() => {
        const partitions = new PartitionConfig(languages);

        it('should return invariant partitions', () => {
            const result = partitions.getAll(createField({ id: 3, properties: createProperties('String'), partitioning: 'invariant' }));

            expect(result).toEqual([
                { key: 'iv', isOptional: false },
            ]);
        });

        it('should return language partitions', () => {
            const result = partitions.getAll(createField({ id: 3, properties: createProperties('String') }));

            expect(result).toEqual([
                { key: 'en', isOptional: false },
                { key: 'de', isOptional: true },
            ]);
        });

        it('should return partition for language', () => {
            const result = partitions.get(languages[1]);

            expect(result).toEqual({ key: 'de', isOptional: true });
        });

        it('should return partition for no language', () => {
            const result = partitions.get();

            expect(result).toEqual({ key: 'iv', isOptional: false });
        });
    }


() => {
            const result = partitions.getAll(createField({ id: 3, properties: createProperties('String'), partitioning: 'invariant' }));

            expect(result).toEqual([
                { key: 'iv', isOptional: false },
            ]);
        }


() => {
            const result = partitions.getAll(createField({ id: 3, properties: createProperties('String') }));

            expect(result).toEqual([
                { key: 'en', isOptional: false },
                { key: 'de', isOptional: true },
            ]);
        }


() => {
            const result = partitions.get(languages[1]);

            expect(result).toEqual({ key: 'de', isOptional: true });
        }


() => {
            const result = partitions.get();

            expect(result).toEqual({ key: 'iv', isOptional: false });
        }


() => {
            const contentForm = createForm([
                createField({ id: 1, properties: createProperties('String') }),
                createField({ id: 2, properties: createProperties('String'), isDisabled: true }),
            ]);

            expectForm(contentForm.form, 'field1', { disabled: false });
            expectForm(contentForm.form, 'field2', { disabled: true });
        }


() => {
            const contentForm = createForm([
                createField({ id: 3, properties: createProperties('String', { isRequired: true }) }),
            ]);

            expectForm(contentForm.form, 'field3.en', { invalid: true });
            expectForm(contentForm.form, 'field3.de', { invalid: false });
        }


() => {
            const contentForm = createForm([
                createField({ id: 1, properties: createProperties('Number'), partitioning: 'invariant' }),
                createField({ id: 2, properties: createProperties('Number'), partitioning: 'invariant' }),
            ], [{
                field: 'field1', action: 'Require', condition: 'ctx.value < 100',
            }]);

            contentForm.setContext({ value: 50 });

            const field1 = contentForm.get('field1')!.get('iv');

            expect(field1!.form.valid).toBeFalsy();

            contentForm.setContext({ value: 120 });

            expect(field1!.form.valid).toBeTruthy();
        }


() => {
            const contentForm = createForm([
                createField({ id: 1, properties: createProperties('Number'), partitioning: 'invariant' }),
                createField({ id: 2, properties: createProperties('Number'), partitioning: 'invariant' }),
            ], [{
                field: 'field1', action: 'Require', condition: 'data.field2.iv < 100',
            }]);

            const field1 = contentForm.get('field1')!.get('iv');
            const field2 = contentForm.get('field2');

            expect(field1!.form.valid).toBeFalsy();

            contentForm.load({
                field2: {
                    iv: 120,
                },
            });

            expect(field1!.form.valid).toBeTruthy();

            field2?.get('iv')!.form.setValue(99);

            expect(field1!.form.valid).toBeFalsy();
        }


() => {
            const contentForm = createForm([
                createField({ id: 1, properties: createProperties('Number'), partitioning: 'invariant' }),
                createField({ id: 2, properties: createProperties('Number'), partitioning: 'invariant' }),
            ], [{
                field: 'field1', action: 'Disable', condition: 'data.field2.iv > 100',
            }]);

            const field1 = contentForm.get('field1');
            const field1_iv = contentForm.get('field1')!.get('iv');

            const field2 = contentForm.get('field2');

            expect(field1!.form.disabled).toBeFalsy();

            contentForm.load({
                field1: {
                    iv: 120,
                },
                field2: {
                    iv: 120,
                },
            });

            expect(field1!.form.disabled).toBeTruthy();
            expect(field1_iv!.form.disabled).toBeTruthy();

            field2?.get('iv')!.form.setValue(99);

            expect(field1!.form.disabled).toBeFalsy();
            expect(field1_iv!.form.disabled).toBeFalsy();
        }


() => {
            const contentForm = createForm([
                createField({ id: 1, properties: createProperties('Number'), partitioning: 'invariant' }),
                createField({ id: 2, properties: createProperties('Number'), partitioning: 'invariant' }),
            ], [{
                field: 'field1', action: 'Hide', condition: 'data.field2.iv > 100',
            }]);

            const field1 = contentForm.get('field1');
            const field1_iv = contentForm.get('field1')!.get('iv');

            const field2 = contentForm.get('field2');

            expect(field1!.hidden).toBeFalsy();

            contentForm.load({
                field1: {
                    iv: 120,
                },
                field2: {
                    iv: 120,
                },
            });

            expect(field1!.hidden).toBeTruthy();
            expect(field1_iv!.hidden).toBeTruthy();

            field2?.get('iv')!.form.setValue(99);

            expect(field1!.hidden).toBeFalsy();
            expect(field1_iv!.hidden).toBeFalsy();
        }


() => {
            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Array'),
                    partitioning: 'invariant',
                    nested: [
                        createNestedField({ id: 41, properties: createProperties('Number') }),
                        createNestedField({ id: 42, properties: createProperties('Number') }),
                    ],
                }),
            ], [{
                field: 'field4.nested42', action: 'Disable', condition: 'itemData.nested41 > 100',
            }]);

            const array = contentForm.get(complexSchema.fields[3])!.get(languages[0]) as FieldArrayForm;

            contentForm.load({
                field4: {
                    iv: [{
                        nested41: 120,
                        nested42: 120,
                    }, {
                        nested41: 99,
                        nested42: 99,
                    }],
                },
            });

            expect(array.get(0)!.get('nested42')!.form.disabled).toBeTruthy();
            expect(array.get(1)!.get('nested42')!.form.disabled).toBeFalsy();
        }


() => {
            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Array'),
                    partitioning: 'invariant',
                    nested: [
                        createNestedField({ id: 41, properties: createProperties('Number') }),
                        createNestedField({ id: 42, properties: createProperties('Number') }),
                    ],
                }),
            ], [{
                field: 'field4.nested42', action: 'Hide', condition: 'itemData.nested41 > 100',
            }]);

            const array = contentForm.get(complexSchema.fields[3])!.get(languages[0]) as FieldArrayForm;

            contentForm.load({
                field4: {
                    iv: [{
                        nested41: 120,
                        nested42: 120,
                    }, {
                        nested41: 99,
                        nested42: 99,
                    }],
                },
            });

            expect(array.get(0)!.get('nested42')!.hidden).toBeTruthy();
            expect(array.get(1)!.get('nested42')!.hidden).toBeFalsy();
        }


() => {
            const componentId = MathHelper.guid();
            const component = createSchema({
                id: 2,
                fields: [
                    createField({
                        id: 1,
                        properties: createProperties('String'),
                        partitioning: 'invariant',
                    }),
                ],
                fieldRules: [{
                    field: 'field1', action: 'Hide', condition: 'data.field1 > 100',
                }],
            });

            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Components'),
                    partitioning: 'invariant',
                }),
            ], [], {
                [componentId]: component,
            });

            const array = contentForm.get(complexSchema.fields[3])!.get(languages[0]) as FieldArrayForm;

            contentForm.load({
                field4: {
                    iv: [{
                        schemaId: componentId,
                        field1: 120,
                    }, {
                        schemaId: componentId,
                        field1: 99,
                    }],
                },
            });

            // Should hide fields.
            expect(array.get(0)!.get('field1')!.hidden).toBeTruthy();
            expect(array.get(1)!.get('field1')!.hidden).toBeFalsy();
        }


() => {
            const componentId = MathHelper.guid();
            const component = createSchema({
                id: 1,
                fields: [
                    createField({
                        id: 11,
                        properties: createProperties('String', {
                            defaultValue: 'Initial',
                        }),
                        partitioning: 'invariant',
                    }),
                    createField({
                        id: 12,
                        properties: createProperties('Number', {
                            defaultValue: 12,
                        }),
                        partitioning: 'invariant',
                    }),
                ],
            });

            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Component'),
                    partitioning: 'invariant',
                }),
            ], [], {
                [componentId]: component,
            });

            contentForm.load({});

            // Should be undefined by default.
            expect(contentForm.value).toEqual({
                field4: {
                    iv: undefined,
                },
            });

            (contentForm.get('field4')?.get('iv') as ComponentForm).selectSchema(componentId);

            // Should add field from component.
            expect(contentForm.value).toEqual({
                field4: {
                    iv: {
                        schemaId: componentId,
                        field11: 'Initial',
                        field12: 12,
                    },
                },
            });
        }


() => {
            const component1Id = MathHelper.guid();
            const component1 = createSchema({
                id: 1,
                fields: [
                    createField({
                        id: 11,
                        properties: createProperties('String'),
                        partitioning: 'invariant',
                    }),
                ],
            });

            const component2Id = MathHelper.guid();
            const component2 = createSchema({
                id: 2,
                fields: [
                    createField({
                        id: 21,
                        properties: createProperties('String'),
                        partitioning: 'invariant',
                    }),
                ],
            });

            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Component'),
                    partitioning: 'invariant',
                }),
            ], [], {
                [component1Id]: component1,
                [component2Id]: component2,
            });

            contentForm.load({});

            // Should be undefined by default.
            expect(contentForm.value).toEqual({
                field4: {
                    iv: undefined,
                },
            });

            contentForm.load({
                field4: {
                    iv: {
                        schemaId: component1Id,
                    },
                },
            });

            // Should add field from component1.
            expect(contentForm.value).toEqual({
                field4: {
                    iv: {
                        schemaId: component1Id,
                        field11: null,
                    },
                },
            });

            contentForm.load({
                field4: {
                    iv: {
                        schemaId: component2Id,
                    },
                },
            });

            // Should add field from component1.
            expect(contentForm.value).toEqual({
                field4: {
                    iv: {
                        schemaId: component2Id,
                        field21: null,
                    },
                },
            });
        }


() => {
            const componentId = MathHelper.guid();
            const component = createSchema({
                id: 1,
                fields: [
                    createField({
                        id: 11,
                        properties: createProperties('String'),
                        partitioning: 'invariant',
                    }),
                ],
            });

            const contentForm = createForm([
                createField({
                    id: 4,
                    properties: createProperties('Component'),
                    partitioning: 'invariant',
                }),
            ], [], {
                [componentId]: component,
            });

            contentForm.load({
                field4: {
                    iv: {
                        schemaId: 'invalid',
                    },
                },
            });

            // Should ignore invalid id.
            expect(contentForm.value).toEqual({
                field4: {
                    iv: {},
                },
            });
        }


() => {
            const { contentForm, array } = createArrayFormWith2Items();

            contentForm.load({
                field4: {
                    iv: [{
                        nested41: 'Text',
                    }],
                },
            });

            const nestedItem = array.form.get([0])!;

            expectLength(array, 1);
            expectForm(nestedItem, 'nested41', { disabled: false, value: 'Text' });
            expectForm(nestedItem, 'nested42', { disabled: true, value: null });
        }


() => {
            const { array } = createArrayFormWith2Items();

            array.addItem();

            const nestedItem = array.form.get([2])!;

            expectLength(array, 3);
            expectForm(nestedItem, 'nested41', { disabled: false, value: null });
            expectForm(nestedItem, 'nested42', { disabled: true, value: 'Default' });
        }


() => {
            const { array } = createArrayFormWith2Items();

            array.sort([array.get(1), array.get(0)]);

            expectLength(array, 2);
            expect(array.form.value).toEqual([{ nested41: 'Text2', nested42: null }, { nested41: 'Text1', nested42: null }]);
        }


() => {
            const { array } = createArrayFormWith2Items();

            array.removeItemAt(0);

            expectLength(array, 1);
            expect(array.form.value).toEqual([{ nested41: 'Text2', nested42: null }]);
        }


() => {
            const { array } = createArrayFormWith2Items();

            array.setValue([]);

            expectLength(array, 0);
            expect(array.form.value).toEqual([]);
        }


() => {
            const { array } = createArrayFormWith2Items();

            array.unset();

            expectLength(array, 0);
            expect(array.form.value).toEqual(undefined);
        }


() => {
            const contentForm = createForm([
                createField({ id: 4, properties: createProperties('Array'), partitioning: 'invariant' }),
            ]);

            const nestedForm = contentForm.form.get('field4.iv') as FormArray;

            expect(nestedForm.controls.length).toBe(0);
        }


() => {
        const simpleForm = createForm([
            createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
        ]);

        const hasChanged = simpleForm.hasChanges({ field1: { iv: 'other' } });

        expect(hasChanged).toBeTruthy();
    }


() => {
        const simpleForm = createForm([
            createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
        ]);

        const hasChanged = simpleForm.hasChanges({ field1: { iv: null } });

        expect(hasChanged).toBeFalsy();
    }


() => {
        let simpleForm: EditContentForm;

        beforeEach(() => {
            simpleForm = createForm([
                createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
            ]);
        });

        it('should not be an unsaved change if nothing has changed', () => {
            expect(simpleForm.hasChanged()).toBeFalsy();
        });

        it('should be an unsaved change if value has changed but not saved', () => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });

            expect(simpleForm.hasChanged()).toBeTruthy();
        });

        it('should not be an unsaved change if value has changed and saved', () => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });
            simpleForm.submit();
            simpleForm.submitCompleted();

            expect(simpleForm.hasChanged()).toBeFalsy();
        });

        it('should subscribe to values', () => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });

            let value: any;

            simpleForm.valueChanges.subscribe(result => {
                value = result;
            });

            expect(value).toEqual({ field1: { iv: 'Change' } });
        });
    }


() => {
            simpleForm = createForm([
                createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
            ]);
        }


() => {
            expect(simpleForm.hasChanged()).toBeFalsy();
        }


() => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });

            expect(simpleForm.hasChanged()).toBeTruthy();
        }


() => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });
            simpleForm.submit();
            simpleForm.submitCompleted();

            expect(simpleForm.hasChanged()).toBeFalsy();
        }


() => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });

            let value: any;

            simpleForm.valueChanges.subscribe(result => {
                value = result;
            });

            expect(value).toEqual({ field1: { iv: 'Change' } });
        }


result => {
                value = result;
            }


() => {
        let simpleForm: EditContentForm;

        beforeEach(() => {
            simpleForm = createForm([
                createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
            ]);
            simpleForm.load({ field1: { iv: 'Initial' } }, true);
        });

        it('should not be an unsaved change if nothing has changed', () => {
            simpleForm.load({ field1: { iv: 'Initial' } }, true);

            expect(simpleForm.hasChanged()).toBeFalsy();
        });

        it('should be an unsaved change if value has changed but not saved', () => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });

            expect(simpleForm.hasChanged()).toBeTruthy();
        });

        it('should be an unsaved change if value has been loaded but not saved', () => {
            simpleForm.load({ field1: { iv: 'Prev' } });

            expect(simpleForm.hasChanged()).toBeTruthy();
        });

        it('should not be an unsaved change if value has changed and saved', () => {
            simpleForm.form.setValue({ field1: { iv: 'Change' } });
            simpleForm.submit();
            simpleForm.submitCompleted();

            expect(simpleForm.hasChanged()).toBeFalsy();
        });

        it('should not be an unsaved change if value has been loaded but not saved', () => {
            simpleForm.load({ field1: { iv: 'Prev' } });
            simpleForm.submit();
            simpleForm.submitCompleted();

            expect(simpleForm.hasChanged()).toBeFalsy();
        });
    }


() => {
            simpleForm = createForm([
                createField({ id: 1, properties: createProperties('String'), partitioning: 'invariant' }),
            ]);
            simpleForm.load({ field1: { iv: 'Initial' } }, true);
        }


() => {
            simpleForm.load({ field1: { iv: 'Initial' } }, true);

            expect(simpleForm.hasChanged()).toBeFalsy();
        }


() => {
            simpleForm.load({ field1: { iv: 'Prev' } });

            expect(simpleForm.hasChanged()).toBeTruthy();
        }


() => {
            simpleForm.load({ field1: { iv: 'Prev' } });
            simpleForm.submit();
            simpleForm.submitCompleted();

            expect(simpleForm.hasChanged()).toBeFalsy();
        }


/* eslint-disable sort-keys */
// The available endpoints that will show in the dropdown. For the most part (with the exception of
// Polkadot) we try to keep this to live chains only, with RPCs hosted by the community/chain vendor
//   info: The chain logo name as defined in ../ui/logos/index.ts in namedLogos (this also needs to align with @polkadot/networks)
//   text: The text to display on the dropdown
//   value: The actual hosted secure websocket endpoint
export function createKusama (t: TFunction): EndpointOption {
  return {
    dnslink: 'kusama',
    genesisHash: KUSAMA_GENESIS,
    info: 'kusama',
    text: t('rpc.kusama.parity', 'Kusama', { ns: 'apps-config' }),
    providers: {
      Parity: 'wss://kusama-rpc.polkadot.io',
      OnFinality: 'wss://kusama.api.onfinality.io/public-ws',
      'Patract Elara': 'wss://kusama.elara.patract.io',
      'light client': 'light://substrate-connect/kusama'
      // Pinknode: 'wss://rpc.pinknode.io/kusama/explorer' // https://github.com/polkadot-js/apps/issues/5721
    },
    teleport: [1000],
    linked: [
      // (1) all system parachains (none available yet)
      // ...
      // (2) all common good parachains
/*      {
        info: 'statemine',
        paraId: 1000,
        text: t('rpc.kusama.statemine', 'Statemine', { ns: 'apps-config' }),
        providers: {
          Parity: 'wss://kusama-statemine-rpc.paritytech.net',
          OnFinality: 'wss://statemine.api.onfinality.io/public-ws',
          'Patract Elara': 'wss://statemine.kusama.elara.patract.io'
        },
        teleport: [-1]
      },  */
      /// (3) parachains with id, see Rococo (info here maps to the actual "named icon")
      //
      // NOTE: Added alphabetical based on chain name
      {
        info: 'robonomics',
        homepage: 'http://robonomics.network/',
        paraId: 2077,
        text: t('rpc.kusama.robonomics', 'Robonomics', { ns: 'apps-config' }),
        providers: {
          Airalab: 'wss://kusama.rpc.robonomics.network/'
        }
      }
    ]
  };
}


export function spawn(
  initFunc: (actor: any, ...args: any[]) => void,
  ...args: any[]
) {
  const actor = new actorClass();
  actor.init(initFunc, ...args);
  return actor;
}


export function addUpdater(
  updateFunc: (updater: Updater, actor?: Actor) => void,
  interval = 1
) {
  const updater = new Updater(updateFunc, interval);
  updaterPool.add(updater);
  return updater;
}


export function update() {
  pool.update();
  updaterPool.update();
}


export function reset() {
  pool.removeAll();
  updaterPool.removeAll();
}


export function setActorClass(_actorClass: any) {
  actorClass = _actorClass;
}


function stableSort(values: any[], compareFunc: Function) {
  const indexedValues = values.map((v, i) => [v, i]);
  indexedValues.sort((a, b) => {
    const cmp = compareFunc(a[0], b[0]);
    return cmp !== 0 ? cmp : a[1] - b[1];
  });
  return indexedValues.map(v => v[0]);
}


(a: any, b: any) => b.priority - a.priority


a => a.isAlive


a => a.func === func && a.isAlive


a => {
      a.remove();
    }


(v, i) => [v, i]


(a, b) => {
    const cmp = compareFunc(a[0], b[0]);
    return cmp !== 0 ? cmp : a[1] - b[1];
  }


v => v[0]


export class Actor {
  pool = pool;
  func!: Function;
  isAlive = true;
  ticks = 0;
  updaterPool = new Pool();
  onRemove: Function | undefined;
  priority = 1;

  remove() {
    if (!this.isAlive) {
      return;
    }
    if (this.onRemove != null) {
      this.onRemove();
    }
    this.isAlive = false;
  }

  addUpdater(
    updateFunc: (updater: Updater, actor?: Actor) => void,
    interval = 1
  ) {
    const updater: Updater = new Updater(updateFunc, interval, this);
    this.updaterPool.add(updater);
    return updater;
  }

  setPool(pool: Pool) {
    this.pool = pool;
  }

  setPriority(priority: number) {
    this.priority = priority;
    this.pool.enablePriority();
  }

  init(initFunc: (actor: any, ...args: any[]) => void, ...args: any[]) {
    this.func = initFunc;
    initFunc(this, ...args);
    this.pool.add(this);
  }

  update() {
    this.updaterPool.update();
    this.ticks++;
  }
}


export class Updater {
  func: Function;
  isAlive = true;
  ticks = 0;
  intervalTicks = 0;

  remove() {
    this.isAlive = false;
  }

  setInterval(interval: number) {
    this.interval = interval;
  }

  constructor(
    public updateFunc: (updater: Updater, actor?: Actor) => void,
    public interval: number,
    public actor?: Actor
  ) {
    this.func = updateFunc;
  }

  update() {
    this.intervalTicks--;
    if (this.intervalTicks <= 0) {
      this.updateFunc(this, this.actor);
      this.intervalTicks = this.interval;
    }
    this.ticks++;
  }
}


export class Pool {
  instances: UpdatedInstance[] = [];
  isRemovingAllInstances = false;
  isPriorityEnabled = false;

  add(instance: UpdatedInstance) {
    this.instances.push(instance);
  }

  update() {
    if (this.isPriorityEnabled) {
      this.instances = stableSort(
        this.instances,
        (a: any, b: any) => b.priority - a.priority
      );
    }
    for (let i = 0; i < this.instances.length; ) {
      if (this.isRemovingAllInstances) {
        this.isRemovingAllInstances = false;
        break;
      }
      const instance = this.instances[i];
      if (instance.isAlive) {
        instance.update();
      }
      if (instance.isAlive) {
        i++;
      } else {
        this.instances.splice(i, 1);
      }
    }
  }

  get(func?: Function) {
    return func == null
      ? this.instances.filter(a => a.isAlive)
      : this.instances.filter(a => a.func === func && a.isAlive);
  }

  removeAll() {
    this.instances.forEach(a => {
      a.remove();
    });
    this.instances = [];
    this.isRemovingAllInstances = true;
  }

  enablePriority() {
    this.isPriorityEnabled = true;
  }
}


export interface AnyActor extends Actor {
  [key: string]: any;
}


export interface UpdatedInstance {
  func: Function;
  isAlive: boolean;
  update: Function;
  remove: Function;
  priority?: number;
}


remove() {
    if (!this.isAlive) {
      return;
    }
    if (this.onRemove != null) {
      this.onRemove();
    }
    this.isAlive = false;
  }


addUpdater(
    updateFunc: (updater: Updater, actor?: Actor) => void,
    interval = 1
  ) {
    const updater: Updater = new Updater(updateFunc, interval, this);
    this.updaterPool.add(updater);
    return updater;
  }


setPool(pool: Pool) {
    this.pool = pool;
  }


setPriority(priority: number) {
    this.priority = priority;
    this.pool.enablePriority();
  }


init(initFunc: (actor: any, ...args: any[]) => void, ...args: any[]) {
    this.func = initFunc;
    initFunc(this, ...args);
    this.pool.add(this);
  }


update() {
    this.updaterPool.update();
    this.ticks++;
  }


remove() {
    this.isAlive = false;
  }


setInterval(interval: number) {
    this.interval = interval;
  }


update() {
    this.intervalTicks--;
    if (this.intervalTicks <= 0) {
      this.updateFunc(this, this.actor);
      this.intervalTicks = this.interval;
    }
    this.ticks++;
  }


add(instance: UpdatedInstance) {
    this.instances.push(instance);
  }


update() {
    if (this.isPriorityEnabled) {
      this.instances = stableSort(
        this.instances,
        (a: any, b: any) => b.priority - a.priority
      );
    }
    for (let i = 0; i < this.instances.length; ) {
      if (this.isRemovingAllInstances) {
        this.isRemovingAllInstances = false;
        break;
      }
      const instance = this.instances[i];
      if (instance.isAlive) {
        instance.update();
      }
      if (instance.isAlive) {
        i++;
      } else {
        this.instances.splice(i, 1);
      }
    }
  }


get(func?: Function) {
    return func == null
      ? this.instances.filter(a => a.isAlive)
      : this.instances.filter(a => a.func === func && a.isAlive);
  }


removeAll() {
    this.instances.forEach(a => {
      a.remove();
    });
    this.instances = [];
    this.isRemovingAllInstances = true;
  }


enablePriority() {
    this.isPriorityEnabled = true;
  }


export declare class FocusTrapZone extends BaseComponent<IFocusTrapZoneProps, {}> implements IFocusTrapZone {
    private static _focusStack;
    private _root;
    private _firstBumper;
    private _lastBumper;
    private _hasFocus;
    private _previouslyFocusedElementOutsideTrapZone;
    private _previouslyFocusedElementInTrapZone?;
    private _hasFocusHandler;
    private _hasClickHandler;
    componentDidMount(): void;
    componentWillReceiveProps(nextProps: IFocusTrapZoneProps): void;
    componentDidUpdate(prevProps: IFocusTrapZoneProps): void;
    componentWillUnmount(): void;
    render(): JSX.Element;
    focus(): void;
    private _focusAsync(element);
    private _onRootFocus;
    private _onRootBlur;
    private _onFirstBumperFocus;
    private _onLastBumperFocus;
    private _onBumperFocus;
    private _bringFocusIntoZone();
    private _returnFocusToInitiator();
    private _updateEventHandlers(newProps);
    private _onFocusCapture;
    private _isBumper(element);
    private _forceFocusInTrap(ev);
    private _forceClickInTrap(ev);
}


componentDidMount(): void;


componentWillReceiveProps(nextProps: IFocusTrapZoneProps): void;


componentDidUpdate(prevProps: IFocusTrapZoneProps): void;


componentWillUnmount(): void;


focus(): void;


private _focusAsync(element);


private _bringFocusIntoZone();


private _returnFocusToInitiator();


private _updateEventHandlers(newProps);


private _isBumper(element);


private _forceFocusInTrap(ev);


private _forceClickInTrap(ev);


(resolve) => {this.resolve = resolve;}


(value)=>
		{
			this.tws.watchs().then( (names)=>
			{
				names.push( "" );
				this.names = names;
				this.resolve( true );
			} ).catch( (e)=>{this.cnsle.error(e.message, e); });
		}


(names)=>
			{
				names.push( "" );
				this.names = names;
				this.resolve( true );
			}


(e)=>{this.cnsle.error(e.message, e); }


@Component( {selector: 'watch.main-content.mat-drawer-container', styleUrls: ['watch.css'], templateUrl: './watch.html'} )
export class WatchComponent implements AfterViewInit, OnInit, OnDestroy
{
	constructor( private tws : TwsService, private componentPageTitle:ComponentPageTitle, @Inject('IProfile') private profileService: IProfile, @Inject('IErrorService') private cnsle: IErrorService )
	{}

	ngOnInit()
	{
		this.componentPageTitle.title = this.componentPageTitle.title ? this.componentPageTitle.title+" | Watch" : "Watch";
		this.watchPromise = new Promise<boolean>( (resolve) => {this.resolve = resolve;} );
	};

	ngOnDestroy()
	{
		this.profile.save();
	}

	ngAfterViewInit():void
	{
		this.profile.loadedPromise.then( (value)=>
		{
			this.tws.watchs().then( (names)=>
			{
				names.push( "" );
				this.names = names;
				this.resolve( true );
			} ).catch( (e)=>{this.cnsle.error(e.message, e); });
		});
	}
	names:string[];
	names2:string[];
	profile = new Settings<PageSettings>( PageSettings, "WatchComponent", this.profileService );
	private resolve: Function|null = null;
	watchPromise:Promise<boolean>;
}


class PageSettings
{
	assign( value:PageSettings ){ this.sort = value.sort; }
	sort:Sort = {active: "id", direction: "asc"};
}


ngOnInit()
	{
		this.componentPageTitle.title = this.componentPageTitle.title ? this.componentPageTitle.title+" | Watch" : "Watch";
		this.watchPromise = new Promise<boolean>( (resolve) => {this.resolve = resolve;} );
	}


ngOnDestroy()
	{
		this.profile.save();
	}


ngAfterViewInit():void
	{
		this.profile.loadedPromise.then( (value)=>
		{
			this.tws.watchs().then( (names)=>
			{
				names.push( "" );
				this.names = names;
				this.resolve( true );
			} ).catch( (e)=>{this.cnsle.error(e.message, e); });
		});
	}


assign( value:PageSettings ){ this.sort = value.sort; }


export default class ElementsInspector extends Component<{
  onElementExpanded: (key: ElementID, deep: boolean) => void;
  onElementSelected: (key: ElementID) => void;
  onElementHovered:
    | ((key: ElementID | undefined | null) => any)
    | undefined
    | null;
  onValueChanged: ((path: Array<string>, val: any) => any) | undefined | null;
  selected: ElementID | undefined | null;
  focused?: ElementID | undefined | null;
  searchResults?: ElementSearchResultSet | undefined | null;
  root: ElementID | undefined | null;
  elements: {[key: string]: Element};
  useAppSidebar?: boolean;
  alternateRowColor?: boolean;
  contextMenuExtensions?: Array<ContextMenuExtension>;
  decorateRow?: DecorateRow;
}> {
  static defaultProps = {
    alternateRowColor: true,
  };
  render() {
    const {
      selected,
      focused,
      elements,
      root,
      onElementExpanded,
      onElementSelected,
      onElementHovered,
      searchResults,
      alternateRowColor,
      contextMenuExtensions,
      decorateRow,
    } = this.props;

    return (
      <Elements
        onElementExpanded={onElementExpanded}
        onElementSelected={onElementSelected}
        onElementHovered={onElementHovered}
        selected={selected}
        focused={focused}
        searchResults={searchResults}
        root={root}
        elements={elements}
        alternateRowColor={alternateRowColor}
        contextMenuExtensions={contextMenuExtensions}
        decorateRow={decorateRow}
      />
    );
  }
}


export type ElementID = string;


export type ElementSearchResultSet = {
  query: string;
  matches: Set<ElementID>;
};