@iotize/ionic
Version:
Iotize specific building blocks on top of @ionic/angular.
1 lines • 124 kB
Source Map (JSON)
{"version":3,"file":"iotize-ionic-config.mjs","sources":["../../../../projects/iotize-ionic/config/src/lib/utility.ts","../../../../projects/iotize-ionic/config/src/lib/config.ts","../../../../projects/iotize-ionic/config/src/lib/tap-config-item-state.service.ts","../../../../projects/iotize-ionic/config/src/lib/debug.ts","../../../../projects/iotize-ionic/config/src/lib/tap-request-error-to-string.pipe.ts","../../../../projects/iotize-ionic/config/src/lib/tap-resource-enum-translate.pipe.ts","../../../../projects/iotize-ionic/config/src/lib/list-enum-values.pipe.ts","../../../../projects/iotize-ionic/config/src/lib/tap-config-item/tap-config-item.component.ts","../../../../projects/iotize-ionic/config/src/lib/tap-config-item/tap-config-item.component.html","../../../../projects/iotize-ionic/config/src/lib/tap-config-list/tap-config-item-displayed.pipe.ts","../../../../projects/iotize-ionic/config/src/lib/tap-config-list/tap-config-list.component.ts","../../../../projects/iotize-ionic/config/src/lib/tap-config-list/tap-config-list.component.html","../../../../projects/iotize-ionic/config/src/lib/tap-config-parser.ts","../../../../projects/iotize-ionic/config/src/lib/tap-service-call-list.pipe.ts","../../../../projects/iotize-ionic/config/src/lib/tap-config.module.ts","../../../../projects/iotize-ionic/config/src/lib/tap-configuration-mode.service.ts","../../../../projects/iotize-ionic/config/src/iotize-ionic-config.ts"],"sourcesContent":["export function enumToOptions<T extends Object>(\n mapping: T\n): { key: string | number; text: string }[] {\n const keys = Object.keys(mapping).filter(\n (k) => typeof (mapping as any)[k as any] === 'number'\n );\n return keys.map((key) => {\n return { key: (mapping as any)[key], text: key };\n });\n}\n\nexport function tapResourceTranslateKey(path: string, subKey: string) {\n return `tap.lwm2m.paths.${path}.${subKey}`;\n}\n","import { AbstractControl, ValidatorFn, Validators } from '@angular/forms';\nimport {\n bufferToHexString,\n hexStringToBuffer,\n} from '@iotize/common/byte-converter';\nimport {\n FACTORY_RESET_MODE_VERSION,\n Lwm2mEnum,\n TapInfo,\n TAP_MANAGER_APP_ID,\n} from '@iotize/ionic';\nimport {\n HostProtocol,\n SpecialFeature,\n Tap,\n TapResponse,\n TapVersion,\n TargetProtocol,\n} from '@iotize/tap';\nimport { converters } from '@iotize/tap/service/core';\nimport {\n NfcConnectionPriority,\n NfcPairingMode,\n} from '@iotize/tap/service/impl/interface';\nimport { AdpStats } from '@iotize/tap/service/impl/tapnpass';\nimport {\n WifiKeyVisibility,\n WifiMode,\n WifiSSIDVisibility,\n} from '@iotize/tap/service/impl/wifi';\nimport { combineLatest } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { TapConfigItemExtended } from './tap-config-item/tap-config-item.definition';\nimport { enumToOptions } from './utility';\nimport { interfaceServiceGetAppPathResolved } from '@iotize/ionic';\n\nconst TAP_CONFIG_PATTERN = /^([0-9]+)\\.([0-9]+)\\.([0-9]+)$/;\nconst IPV4_PATTERN = /^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$/;\nconst IPV4_MASK_PATTERN = /^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$/;\nconst SSID_PATTERN =\n /^[^ !#;+\\]\\/\"\\t][^+\\]\\/\"\\t]{0,30}[^ !#;+\\]\\/\"\\t]$|^[^ !#;+\\]\\/\"\\t]$/;\n\nfunction regexValidator(pattern: RegExp, message: string): ValidatorFn {\n return (control: AbstractControl): { [key: string]: any } | null => {\n const value = control.value;\n const valueMatchPattern = pattern.test(value);\n return !valueMatchPattern\n ? { invalidFormat: { value: control.value, message } }\n : null;\n };\n}\n\nconst validateTapVersion: ValidatorFn = regexValidator(\n TAP_CONFIG_PATTERN,\n `Must be a valid semantic version (Major.Minor.Patch).`\n);\n\nfunction enumFormatter(data: any): (value: any) => string {\n return (value: any) => {\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return '-';\n }\n return value.map((key) => HostProtocol[key]).join(', ');\n } else {\n return data[value] as string;\n }\n };\n}\n\nexport function isTapConfigured(version: TapVersion) {\n return !(version.major === 0 && version.minor === 0 && version.patch === 0);\n}\n\nfunction stringToEnumArray<T>(v: string, mapping: any): T[] {\n return v\n .split(',')\n .map((item) => item.trim())\n .map((item) => {\n if (!(item in mapping)) {\n throw new Error(\n `Invalid value \"${item}\". Must one of the following: ${Object.keys(\n mapping\n ).join(', ')}`\n );\n }\n return mapping[item as any] as unknown as T;\n });\n}\n\nenum WifiTxPowerEnum {\n 'default' = 0,\n '10 dBm' = 40,\n '12.5 dBm' = 50,\n '15 dBm' = 60,\n '17.5 dBm' = 70,\n '20 dBm' = 80,\n}\n\nconst IPV4_VALIDATOR = regexValidator(\n IPV4_PATTERN,\n `Not a valid IPv4 address (eg 192.168.20.1)`\n);\n\nexport const defaultInfoResolverConfig: TapConfigItemExtended[] = [\n {\n key: TapInfo.HostProtocol,\n input: createEnumField('HostProtocol', HostProtocol),\n viewFormatter: enumFormatter(HostProtocol),\n },\n {\n key: TapInfo.configVersion,\n input: {\n formValidators: [validateTapVersion],\n },\n viewFormatter: (v: string) => {\n if (v === FACTORY_RESET_MODE_VERSION) {\n return 'FACTORY RESET';\n } else if (v === '255.255.65535') {\n return 'CONFIGURATION MODE';\n } else if (v) {\n return `v${v}`;\n } else {\n return 'UNKNOWN';\n }\n },\n editFormatter: {\n read: (input: string): string => {\n return input;\n },\n write: (v: string | undefined) => {\n return v || FACTORY_RESET_MODE_VERSION;\n },\n },\n },\n {\n key: TapInfo.CloudEndpoint,\n getValue: async (tap) => {\n const hostname = (await tap.service.mqtt.getBrokerHostname()).body();\n const port = (await tap.service.mqtt.getBrokerPort()).body();\n return `${hostname}:${port}`;\n },\n putValue: async (tap: Tap, value: string) => {\n const [hostname, port] = value.split(':');\n const setPort = await tap.service.mqtt.putBrokerPort(port || '1883');\n setPort.successful();\n return tap.service.mqtt.putBrokerHostname(hostname);\n },\n },\n {\n key: TapInfo.TargetProtocolConfiguration,\n viewFormatter: (v: Uint8Array) => `${bufferToHexString(v)}`,\n editFormatter: {\n read: (input: string): Uint8Array => {\n return input ? hexStringToBuffer(input) : new Uint8Array();\n },\n write: (v: Uint8Array | undefined) => {\n return v ? bufferToHexString(v) : '';\n },\n },\n getValue: async (tap) => {\n const result = await tap.service.target.getModbusTcpConfiguration();\n result.successful();\n return result.rawBody();\n },\n putValue: async (tap: Tap, value: Uint8Array) => {\n return await tap.lwm2m.put('/1027//21', value);\n },\n },\n {\n input: {\n type: 'toggle',\n },\n key: TapInfo.NFCConnectionPriority,\n getValue: async (tap) => {\n const value = (\n await tap.service.interface.getNfcConnectionPriority()\n ).body();\n return value === NfcConnectionPriority.NFC_PRIORITY;\n },\n putValue: (tap, val: boolean) => {\n const mode = val\n ? NfcConnectionPriority.NFC_NON_PRIORITY\n : NfcConnectionPriority.NFC_PRIORITY;\n return tap.service.interface.putNfcConnectionPriority(mode);\n },\n },\n {\n key: TapInfo.NFCPairingMode,\n input: createEnumField('NfcPairingMode', NfcPairingMode),\n },\n {\n key: TapInfo.DataLogMaxPacketCount,\n viewFormatter: (v: number) => {\n return v === 0 ? 'NO LIMIT' : v.toString();\n },\n input: {\n type: 'number',\n },\n },\n {\n key: TapInfo.IsTargetConnected,\n // viewFormatter: (v: boolean) => v ? 'YES' : 'NO',\n getValue: (tap) => tap.service.target.isConnected(),\n putValue: (tap, enabled: boolean) => {\n if (enabled) {\n return tap.service.target.connect();\n } else {\n return tap.service.target.disconnect();\n }\n },\n input: {\n type: 'toggle',\n },\n },\n {\n key: TapInfo.universalLink,\n icon: 'link',\n },\n {\n key: TapInfo.androidApplicationRecord,\n // TODO replace with getAndroidApplicationId()\n getValue: async (tap) => {\n const appPath = (await tap.service.interface.getAppPath()).body();\n if (appPath.startsWith('$4/')) {\n return appPath.substring(3);\n } else {\n return TAP_MANAGER_APP_ID;\n }\n },\n viewFormatter: (input) => {\n if (input === TAP_MANAGER_APP_ID) {\n return `${input} (Tap Manager)`;\n }\n return input;\n },\n putValue: async (tap, value: string) => {\n return tap.service.interface.putAppPath(`$4/${value}`);\n },\n },\n {\n key: TapInfo.useEncryption,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return lockOptions.scramActivated;\n },\n },\n {\n key: TapInfo.lockFactoryReset,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return !lockOptions.disableHardwareFactoryReset;\n },\n },\n {\n key: TapInfo.InterfaceSecurityScramActivated,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return lockOptions.scramActivated;\n },\n },\n {\n key: TapInfo.InterfaceSecurityDisableHardwareFactoryReset,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return lockOptions.disableHardwareFactoryReset;\n },\n },\n {\n key: TapInfo.InterfaceSecurityDisabledResourceFactoryReset,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return lockOptions.disableResourceFactoryReset;\n },\n },\n {\n key: TapInfo.InterfaceSecurityDisabledLoginWithUID,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return lockOptions.disableLoginWithUID;\n },\n },\n {\n key: TapInfo.hashPassword,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return lockOptions.hashPassword;\n },\n },\n {\n key: TapInfo.isLoginWithUIDEnabled,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return !lockOptions.disableLoginWithUID;\n },\n },\n {\n key: TapInfo.isLWM2MFactoryResetEnabled,\n input: {\n type: 'toggle',\n },\n editable: false,\n getValue: async (tap: Tap) => {\n const lockOptions = (\n await tap.service.interface.getSecurityOptions()\n ).body();\n return !lockOptions.disableResourceFactoryReset;\n },\n },\n {\n key: TapInfo.appPath,\n putValue: (tap: Tap, value: string) => {\n return tap.service.interface.putAppPath(value);\n },\n getValue: (tap) =>\n interfaceServiceGetAppPathResolved.call(tap.service.interface),\n },\n {\n key: TapInfo.TargetProtocol,\n getValue: (tap: Tap) => {\n return tap.service.target.getProtocol();\n },\n putValue: (tap: Tap, protocol: TargetProtocol) => {\n return tap.service.target.putProtocol(protocol);\n },\n setValue: (tap: Tap, protocol: TargetProtocol) => {\n return tap.service.target.setProtocol(protocol);\n },\n viewFormatter: enumFormatter(TargetProtocol),\n input: {\n type: 'select',\n options: enumToOptions(TargetProtocol).filter((f) => {\n return f.key !== TargetProtocol.JTAG;\n }),\n },\n },\n {\n key: '/interface/available-host-protocols',\n viewFormatter: enumFormatter(HostProtocol),\n editFormatter: {\n write: (v: HostProtocol[] | undefined): string => {\n // console.log('Host protocols: ', v);\n if (!v || v.length === 0) {\n return 'NONE';\n }\n return v.map((key) => HostProtocol[key]).join(', ');\n },\n read: (v: string): HostProtocol[] => {\n return stringToEnumArray<HostProtocol>(v, HostProtocol);\n },\n },\n },\n {\n key: TapInfo.authorizedHostProtocols,\n input: {\n type: 'select',\n multiple: true,\n options: [],\n },\n viewFormatter: enumFormatter(HostProtocol),\n init: async (_service, config, infoResolver) => {\n const hostProtocols = await infoResolver.getValue<HostProtocol[]>(\n TapInfo.availableHostProtocols\n );\n if (config.input !== undefined) {\n config.input.options = hostProtocols.map((key) => {\n return { key: key, text: HostProtocol[key] };\n });\n }\n },\n },\n {\n key: TapInfo.AdpVersion,\n getValue: (tap) => tap.service.tapnpass.getStatus(),\n viewFormatter: (stats: AdpStats) => {\n const v = stats.header.version;\n return `${v.major}.${v.minor}.${v.patch}`;\n },\n },\n {\n key: TapInfo.WifiHostname,\n viewFormatter: (v: string) => {\n if (v === '0.0.0.0') {\n return 'NO IP';\n }\n return v;\n },\n },\n {\n key: TapInfo.WifiSSID,\n input: {\n formValidators: [\n regexValidator(\n SSID_PATTERN,\n `This is not a valid SSID. It can be any alphanumeric, case-sensitive entry from 1 to 32 characters. Trailing or leading spaces are not allowed.`\n ),\n ],\n },\n },\n {\n key: TapInfo.WifiSSIDVisibility,\n input: createEnumField('WifiSSIDVisibility', WifiSSIDVisibility),\n viewFormatter: enumFormatter(WifiSSIDVisibility),\n },\n {\n key: TapInfo.WifiKeyVisibility,\n input: createEnumField('WifiKeyVisibility', WifiKeyVisibility),\n viewFormatter: enumFormatter(WifiKeyVisibility),\n },\n {\n key: TapInfo.WifiTxPower,\n viewFormatter: enumFormatter(WifiTxPowerEnum),\n input: createEnumField('WifiTxPowerEnum', WifiTxPowerEnum),\n },\n {\n key: TapInfo.NetworkInfraIp,\n input: {\n formValidators: [IPV4_VALIDATOR],\n },\n viewFormatter: (value) => {\n if (value === '0.0.0.0') {\n return 'Dynamic (DHCP)';\n }\n return value;\n },\n },\n {\n key: TapInfo.NetworkIpMask,\n input: {\n formValidators: [\n regexValidator(\n IPV4_MASK_PATTERN,\n `Not a valid IPv4 mask (eg 192.168.20.1)`\n ),\n ],\n },\n isDisplayed: ({ tapConfigItemStateService }) => {\n return combineLatest([\n tapConfigItemStateService.valueChange<WifiMode>(TapInfo.WifiMode),\n tapConfigItemStateService.valueChange<string>(TapInfo.NetworkInfraIp),\n ]).pipe(\n map(([mode, ip]) => {\n return mode === WifiMode.NETWORK && ip !== '0.0.0.0';\n })\n );\n },\n },\n {\n key: TapInfo.NetworkGatewayIp,\n input: {\n formValidators: [IPV4_VALIDATOR],\n },\n isDisplayed: ({ tapConfigItemStateService }) => {\n return combineLatest([\n tapConfigItemStateService.valueChange<WifiMode>(TapInfo.WifiMode),\n tapConfigItemStateService.valueChange<string>(TapInfo.NetworkInfraIp),\n ]).pipe(\n map(([mode, ip]) => {\n return mode === WifiMode.NETWORK && ip !== '0.0.0.0';\n })\n );\n },\n },\n {\n key: TapInfo.NetworkDNSIp,\n input: {\n formValidators: [IPV4_VALIDATOR],\n },\n isDisplayed: ({ tapConfigItemStateService }) => {\n return combineLatest([\n tapConfigItemStateService.valueChange<WifiMode>(TapInfo.WifiMode),\n tapConfigItemStateService.valueChange<string>(TapInfo.NetworkInfraIp),\n ]).pipe(\n map(([mode, ip]) => {\n return mode === WifiMode.NETWORK && ip !== '0.0.0.0';\n })\n );\n },\n },\n {\n key: TapInfo.WifiMode,\n input: createEnumField('WifiMode', WifiMode),\n },\n {\n key: TapInfo.isHostProtocolAuthorized,\n input: {\n type: 'toggle',\n },\n getValue: async (tap: Tap, protocol: HostProtocol): Promise<boolean> => {\n if (protocol === undefined) {\n throw new Error('Illegal argument error: missing protocol');\n }\n const protocols = (\n await tap.service.interface.getAuthorizedHostProtocol()\n ).body();\n return protocols.find((p) => p === protocol) !== undefined;\n },\n putValue: async (tap: Tap, value: boolean, protocol: HostProtocol) => {\n const protocols = (\n await tap.service.interface.getAuthorizedHostProtocol()\n ).body();\n if (protocol === undefined) {\n throw new Error('Illegal argument error: missing protocol');\n }\n const indexOfProtocol = protocols.findIndex((p) => p === protocol);\n if (value) {\n if (indexOfProtocol === -1) {\n protocols.push(protocol);\n }\n } else {\n if (indexOfProtocol >= 0) {\n protocols.splice(indexOfProtocol, 1);\n }\n }\n return tap.service.interface.putAuthorizedHostProtocol(protocols);\n },\n },\n {\n key: TapInfo.WifiKey,\n input: {\n type: 'password',\n formValidators: [Validators.minLength(8), Validators.maxLength(128)],\n },\n },\n {\n key: TapInfo.variableMetaData,\n input: {},\n getValue: async (tap: Tap, variableId: number) => {\n const metaData = (\n await tap.service.variable.getRawMeta(variableId)\n ).body();\n return converters.ascii.decode(metaData);\n },\n putValue: (tap: Tap, value: string, bundleId: number) => {\n const data = converters.ascii.encode(value);\n return tap.service.variable.putRawMeta(bundleId, data);\n },\n },\n {\n key: TapInfo.profilePassword,\n input: {\n type: 'password',\n formValidators: [Validators.minLength(1), Validators.maxLength(16)],\n },\n viewFormatter: () => {\n return '********';\n },\n putValue: async (tap: Tap, value: string, profileId?: number) => {\n await tap.auth.changePassword(value, profileId);\n return TapResponse.SUCCESS();\n },\n },\n {\n key: TapInfo.DeviceMemoryFree,\n viewFormatter(value) {\n return Math.floor((value / 1024) * 100) / 100 + ' KB';\n },\n },\n {\n key: TapInfo.TimeLocalTime,\n viewFormatter(localTime) {\n if (!localTime) {\n return '';\n }\n const date = new Date(Date.UTC(localTime.year, 0, localTime.dayOfYear));\n date.setHours(localTime.hours);\n date.setMinutes(localTime.minutes);\n date.setSeconds(localTime.seconds);\n // {\"seconds\":54,\"minutes\":3,\"hours\":8,\"dayOfMonth\":8,\"month\":8,\"year\":122,\"dayOfWeek\":4,\"dayOfYear\":250,\"isdst\":0}\n return `${date.toUTCString()}`;\n },\n },\n {\n key: TapInfo.InterfaceSpecialFeatureSWDDirect,\n ...createSpecialFeatureDAO(SpecialFeature.SWD_DIRECT_ACCESS),\n },\n {\n key: TapInfo.InterfaceSpecialFeatureSerial,\n ...createSpecialFeatureDAO(SpecialFeature.SERIAL_ACCESS),\n },\n {\n key: TapInfo.InterfaceSpecialFeatureModbusDirect,\n ...createSpecialFeatureDAO(SpecialFeature.MODBUS_DIRECT_ACCESS),\n },\n {\n key: '/wifi/enabled',\n input: {\n type: 'toggle',\n },\n getValue: async (tap) => {\n const response = await tap.service.wifi.getDisabled();\n return !response.body();\n },\n putValue: (tap, enabled: boolean) => {\n return tap.service.wifi.putDisabled(!enabled);\n },\n },\n];\n\nfunction createSpecialFeatureDAO(\n feature: SpecialFeature\n): Partial<TapConfigItemExtended> {\n return {\n input: {\n type: 'number',\n formValidators: [Validators.min(0), Validators.max(65535)],\n },\n getValue: async (tap: Tap) => {\n const result = (\n await tap.service.interface.getSpecialFeatureProfile(feature)\n ).body();\n if (result === 65535 - (feature - 100)) {\n return 'UNAUTHORIZED';\n } else {\n return result;\n }\n },\n putValue: async (tap: Tap, body: number) => {\n return await tap.service.interface.putSpecialFeatureProfile(\n feature,\n body\n );\n },\n };\n}\n\nfunction createEnumField(\n enumId: string,\n enumData: Lwm2mEnum\n): Required<TapConfigItemExtended>['input'] {\n return {\n type: 'select',\n enum: {\n id: enumId,\n data: enumData,\n },\n };\n}\n","import { Injectable } from '@angular/core';\nimport { FormControl, ValidationErrors } from '@angular/forms';\nimport { ProgressState } from '@iotize/common/progress/api';\nimport { deepCopy, deepEqual } from '@iotize/common/utility';\nimport {\n TapInfo,\n TapInfoCacheService,\n TapInfoConfigService,\n TapInfoDAOService,\n TapInfoKey,\n TapInfoKeyObjectOrString,\n createCacheKey,\n toTapInfoKeyObject,\n} from '@iotize/ionic';\nimport { BehaviorSubject, Observable, Subject, concat, defer, of } from 'rxjs';\nimport {\n distinctUntilChanged,\n map,\n shareReplay,\n switchMap,\n tap,\n} from 'rxjs/operators';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class TapConfigItemStateService {\n private _pendingChanges = new BehaviorSubject<\n {\n key: TapInfoKey;\n value: any;\n }[]\n >([]);\n\n loading: boolean = false;\n\n hasPendingChanges: Observable<boolean> = this._pendingChanges.pipe(\n map((changes) => changes.length > 0)\n );\n\n validationErrorsChange = this._pendingChanges.pipe(\n map((changes) => {\n return changes.reduce<\n Map<\n TapInfoKey,\n {\n value: any;\n errors: ValidationErrors;\n }\n >\n >((acc, change) => {\n const tapConfigItem = this.tapInfoConfigService.findByKey(change.key);\n if (!tapConfigItem) {\n console.warn(`Failed to find Tap Config Item Key \"${change.key}\"`);\n return acc;\n }\n tapConfigItem.input?.formValidators?.forEach((validator) => {\n const error = validator(new FormControl(change.value));\n if (error) {\n acc.set(change.key, {\n value: change.value,\n errors: error,\n });\n }\n });\n return acc;\n }, new Map());\n })\n );\n\n validationErrorsCountChange = this.validationErrorsChange.pipe(\n map((changeErrors) => {\n return Array.from(changeErrors.keys()).length;\n }),\n shareReplay({ bufferSize: 1, refCount: true })\n );\n\n hasValidationErrors = this.validationErrorsCountChange.pipe(\n map((count) => {\n return count > 0;\n })\n );\n\n /**\n * Value change taking into acccount the edited value.\n * If no editaded value for the key, it will take the lasted fetched value from the Tap\n * @param key\n * @returns\n */\n valueChange<T>(key: TapInfoKeyObjectOrString): Observable<T> {\n return this._pendingChanges.pipe(\n switchMap((changes): Observable<T> => {\n const hashKey = createCacheKey(toTapInfoKeyObject(key));\n const editedValue = changes.find(\n (entry) => createCacheKey(entry.key) === hashKey\n );\n if (editedValue !== undefined) {\n return of(editedValue.value as T);\n } else {\n return this.tapInfoCacheService.valueChange<T>(key);\n }\n }),\n distinctUntilChanged(deepEqual),\n shareReplay(1)\n );\n }\n\n getPendingChangeForKey<T = unknown>(\n key: TapInfoKeyObjectOrString\n ): Observable<T | undefined> {\n return this._pendingChanges.pipe(\n map((changes) => {\n const hashKey = createCacheKey(toTapInfoKeyObject(key));\n return changes.find((entry) => createCacheKey(entry.key) === hashKey)\n ?.value;\n }),\n distinctUntilChanged(deepEqual),\n shareReplay(1)\n );\n }\n\n get pendingChangesChange() {\n return this._pendingChanges.asObservable();\n }\n\n private _saveProgress = new Subject<ProgressState>();\n\n get saveProgress() {\n return this._saveProgress.asObservable();\n }\n\n constructor(\n private tapInfoDAOService: TapInfoDAOService,\n private tapInfoCacheService: TapInfoCacheService,\n private tapInfoConfigService: TapInfoConfigService\n ) {}\n\n async savePendingChanges() {\n try {\n this.loading = true;\n await this._savePendingChangesWithProgress().toPromise();\n } finally {\n this.loading = false;\n }\n }\n\n private _savePendingChangesWithProgress() {\n const configItemStateChanges = this.getPendingChangesSnapshot().filter(\n ({ key }) => {\n return key.key !== TapInfo.configVersion;\n }\n );\n const observables = configItemStateChanges.map(({ key, value }) => {\n return defer(async () => {\n await this.tapInfoDAOService.put(key, value);\n this.tapInfoDAOService.get(key).catch((err) => {});\n });\n });\n const total = configItemStateChanges.length;\n const saveProgress = concat(...observables).pipe(\n map((info, index) => {\n return {\n total,\n loaded: index + 1,\n payload: info,\n };\n }),\n tap(this._saveProgress)\n );\n return saveProgress;\n }\n\n clearPendingChanges() {\n this._pendingChanges.next([]);\n }\n\n getPendingChangesSnapshot() {\n return deepCopy(this._pendingChanges.value);\n }\n\n clearPendingChange(tapInfoKey: TapInfoKey) {\n const hashKey = createCacheKey(tapInfoKey);\n const pendingChangesSnapshot = this._pendingChanges.value;\n const pendingChangeIndex = pendingChangesSnapshot.findIndex(\n (entry) => createCacheKey(entry.key) === hashKey\n );\n if (pendingChangeIndex >= 0) {\n pendingChangesSnapshot.splice(pendingChangeIndex, 1);\n this._pendingChanges.next(pendingChangesSnapshot);\n }\n }\n\n setPendingChange(info: TapInfoKey, newValue: any) {\n // Make sure we don't store any other info otherwise deepCopy may failed\n info = {\n key: info.key,\n params: info.params,\n };\n const hashKey = createCacheKey(info);\n const pendingChangesSnapshot = this._pendingChanges.value;\n const item = pendingChangesSnapshot.find(\n (entry) => createCacheKey(entry.key) === hashKey\n );\n if (!item) {\n pendingChangesSnapshot.push({\n key: info,\n value: newValue,\n });\n this._pendingChanges.next(pendingChangesSnapshot);\n } else {\n if (!deepEqual(item.value, newValue)) {\n item.value = newValue;\n this._pendingChanges.next(pendingChangesSnapshot);\n }\n }\n }\n\n removePendingChange(info: TapInfoKey) {\n const hashKey = createCacheKey(info);\n const pendingChangesSnapshot = this._pendingChanges.value;\n const indexOfItem = pendingChangesSnapshot.findIndex(\n (entry) => createCacheKey(entry.key) === hashKey\n );\n\n if (indexOfItem >= 0) {\n pendingChangesSnapshot.splice(indexOfItem, 1);\n this._pendingChanges.next(pendingChangesSnapshot);\n }\n }\n}\n","import { createDebugger } from '@iotize/common/debug';\r\n\r\nexport const debug = createDebugger('@iotize/ionic:config');\r\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { isCodeError } from '@iotize/common/error';\nimport { TapError, TapResponseStatusError } from '@iotize/tap';\nimport { ResultCode } from '@iotize/tap/client/api';\nimport { tapResponseStatusToString } from '@iotize/tap/client/impl';\n\n@Pipe({\n name: 'tapRequestErrorToString',\n})\nexport class TapRequestErrorToStringPipe implements PipeTransform {\n transform(\n value: Error,\n action: 'read' | 'write' = 'read'\n ): string | undefined {\n if (!value) {\n return undefined;\n }\n return userFriendlyError(value, action).message;\n }\n}\n\nfunction userFriendlyError(error: Error, action: 'read' | 'write') {\n if (isCodeError(TapError.Code.ExecuteRequestError, error)) {\n error = (error as TapError).cause || error;\n }\n if (isCodeError(TapResponseStatusError.Code.ResponseStatusError, error)) {\n const tapResponseStatusError = error as TapResponseStatusError;\n const statusCode = tapResponseStatusError.response.status;\n let message: string;\n switch (statusCode) {\n case ResultCode.UNAUTHORIZED:\n message = `You are not authorized to ${action} this value. Try to login first.`;\n break;\n case ResultCode.RESOURCE_LOCKED:\n message = `This value is ${action} protected`;\n break;\n case ResultCode.NOT_IMPLEMENTED:\n message = `This configuration is not available with your current firmware version or Tap model`;\n break;\n default:\n message = tapResponseStatusToString(statusCode);\n }\n error = new Error(message);\n }\n return error;\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { EnumInfo } from '@iotize/ionic';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Observable, of } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\n@Pipe({\n name: 'tapResourceEnumTranslate',\n})\nexport class TapResourceEnumTranslatePipe implements PipeTransform {\n transform(\n enumKey: string | number,\n part: string = 'title',\n meta: EnumInfo | undefined\n ): Observable<string> {\n if (meta) {\n let enumKeyString =\n typeof enumKey === 'number' ? meta.data[enumKey] : enumKey;\n const translationKey = `tap.lwm2m.enums.${meta.id}.${enumKeyString}.${part}`;\n return this.translate.stream(translationKey).pipe(\n map((v) => {\n return v === translationKey ? enumKeyString : v;\n })\n );\n } else {\n return of(enumKey.toString());\n }\n }\n\n constructor(private translate: TranslateService) {}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { listEnumValues } from '@iotize/common/utility';\n\n@Pipe({\n name: 'listEnumValues',\n})\nexport class ListEnumValuesPipe implements PipeTransform {\n transform(enumData: { [key: string | number]: string | number } | undefined) {\n if (!enumData) {\n return [];\n }\n return listEnumValues(enumData);\n }\n}\n","import {\n Component,\n EventEmitter,\n Input,\n OnDestroy,\n OnInit,\n Output,\n} from '@angular/core';\nimport { AbstractControl, FormBuilder } from '@angular/forms';\nimport { ToastController } from '@ionic/angular';\nimport {\n CurrentDeviceService,\n LibError,\n PendingCallManager,\n TapConfigItem,\n TapInfo,\n TapInfoCacheService,\n TapInfoConfigService,\n TapInfoDAOService,\n TapInfoHolder,\n TapInfoRequestService,\n TapResourceKey,\n isTapResultCodeError,\n} from '@iotize/ionic';\nimport { ResultCode } from '@iotize/tap/client/api';\nimport { Subscription } from 'rxjs';\nimport { filter, first } from 'rxjs/operators';\nimport { debug } from '../debug';\nimport { TapConfigItemStateService } from '../tap-config-item-state.service';\n\ntype ColorType = any;\n\n@Component({\n selector: 'tap-config-item',\n templateUrl: './tap-config-item.component.html',\n styleUrls: ['./tap-config-item.component.scss'],\n})\nexport class TapConfigItemComponent<ValueType = any>\n implements OnDestroy, OnInit\n{\n @Output() focusOut: EventEmitter<number> = new EventEmitter<number>();\n @Output() onSubmit: EventEmitter<{\n target: { value: string };\n }> = new EventEmitter();\n pendingCallManager: PendingCallManager;\n\n @Input() editable?: boolean;\n\n @Input() set info(configOrKey: TapConfigItem<ValueType> | TapInfo) {\n this._resetErrors();\n let config: TapConfigItem<ValueType>;\n if (typeof configOrKey === 'object') {\n const defaultConfig = this.tapInfoConfigService.findByKey(configOrKey);\n if (defaultConfig) {\n config = { ...defaultConfig, ...configOrKey };\n } else {\n config = configOrKey;\n }\n } else {\n config = this.tapInfoConfigService.findByKey({\n key: configOrKey as TapResourceKey,\n })!;\n if (!config) {\n console.warn(`Failed to find configuration with key \"${configOrKey}\"`);\n return;\n }\n }\n this.onConfigChange(config);\n this.updateDisplayValue();\n // this.fetchValueIfRequired();\n }\n\n @Input() refresh = false;\n\n @Input() editMode = false;\n\n @Input() globalSubmit = false;\n\n get info(): TapConfigItem<ValueType> {\n return this.config;\n }\n\n get originalValue(): ValueType | undefined {\n return this.config.resolvedValue?.value;\n }\n\n private _valueSubscription?: Subscription;\n private _formValueSubscription?: Subscription;\n private _cancelChangeSubscription?: Subscription;\n\n _config?: TapConfigItem<ValueType>;\n loading = false;\n readError?: Error;\n writeError?: Error;\n\n placeholder = '';\n\n displayedValue?: string;\n\n field: AbstractControl;\n\n ressourceAvailable = true;\n\n isEdited() {\n return this.field.value !== this.originalValue;\n }\n\n get config(): TapConfigItem<ValueType> {\n if (!this._config) {\n throw LibError.componentArgumentRequired(\n 'TapConfigItemComponent',\n 'info'\n );\n }\n return this._config;\n }\n\n get inputOptions() {\n return this.info.input || {};\n }\n\n get isEditable() {\n return (\n this._config?.editable &&\n this.editable &&\n (!this.readError ||\n !isTapResultCodeError(this.readError, ResultCode.NOT_IMPLEMENTED))\n );\n }\n\n constructor(\n formBuilder: FormBuilder,\n private tapService: CurrentDeviceService,\n private toastController: ToastController,\n private infoResolver: TapInfoRequestService,\n private tapInfoDAOService: TapInfoDAOService,\n private tapInfoCacheService: TapInfoCacheService,\n private tapConfigItemState: TapConfigItemStateService,\n private tapInfoConfigService: TapInfoConfigService\n ) {\n const form = formBuilder.nonNullable.group({\n field: [''],\n });\n this.field = form.controls.field;\n this.pendingCallManager = PendingCallManager.create(this.tapService);\n }\n\n private _resetErrors() {\n this.setReadError(undefined);\n this.writeError = undefined;\n this.ressourceAvailable = true;\n }\n\n get pendingEditCall() {\n return this.pendingCallManager.pendingCall;\n }\n\n private onConfigChange(config: TapConfigItem<any>) {\n this._valueSubscription?.unsubscribe();\n this._cancelChangeSubscription?.unsubscribe();\n this._config = config;\n this._cancelChangeSubscription = this.tapConfigItemState\n .getPendingChangeForKey(config)\n .subscribe((v) => {\n if (v === undefined) {\n this.setFieldValue(this.originalValue, {\n emitEvent: false,\n });\n }\n });\n this.initializeConfig();\n if (config.input && config.input.type === 'password') {\n this.setDisplayValue('' as any);\n }\n if (config.input?.formValidators) {\n this.field.setValidators(config.input.formValidators);\n }\n this._valueSubscription = this.tapInfoCacheService\n .infoChange(config)\n .subscribe((newValue: TapInfoHolder) => {\n config.resolvedValue = newValue;\n if (newValue.error !== undefined) {\n this.setReadError(newValue.error);\n } else {\n this.setReadError(undefined);\n if (newValue.value !== undefined) {\n this.setDisplayValue(newValue.value);\n this.setFieldValue(newValue.value);\n } else {\n this.displayedValue = undefined;\n }\n }\n });\n }\n\n initializeConfig() {\n if (this.config.init) {\n this.config\n .init(this.tapService, this.config, this.infoResolver)\n .catch((err) => {\n this.setReadError(err);\n });\n }\n }\n\n setWriteError(error: Error) {\n this.writeError = error;\n }\n\n setReadError(error: Error | undefined) {\n this.readError = error;\n this.ressourceAvailable = error\n ? !isTapResultCodeError(error, ResultCode.NOT_IMPLEMENTED)\n : true;\n }\n\n ngOnInit() {\n this._formValueSubscription = this.field.valueChanges.subscribe(\n (newValue) => {\n if (this.isEdited()) {\n this.tapConfigItemState.setPendingChange(this.info, newValue);\n } else {\n this.tapConfigItemState.removePendingChange(this.info);\n }\n }\n );\n }\n\n ngOnDestroy() {\n this._valueSubscription?.unsubscribe();\n this._formValueSubscription?.unsubscribe();\n this._cancelChangeSubscription?.unsubscribe();\n this.pendingCallManager.destroy();\n }\n\n submit($event: Event) {\n if (!this.globalSubmit) {\n debug('submit', $event);\n this.field.updateValueAndValidity();\n if (!this.field.valid) {\n this.writeError = new Error('Invalid value');\n return;\n }\n // this.onSubmit.emit(event);\n const value = this.getFieldValue();\n // debug(`Creating call new value:`, value, `valid: ${this.field.valid}`)\n const call: () => Promise<any> = async () => {\n await this.tapInfoDAOService.put(this.info, value);\n if (this.info.getValue) {\n this.tapInfoDAOService.get(this.info).catch((err) => {});\n }\n return value;\n };\n this.runCall(call);\n }\n }\n\n private runCall(call: () => Promise<any>) {\n this.loading = true;\n this.pendingCallManager\n .exec(call)\n .then(async (value) => {\n await this.showToast('Value saved!', 'success');\n this.config.resolvedValue = {\n loadDate: new Date(),\n value: value,\n };\n this.writeError = undefined;\n this.editMode = false;\n this.updateDisplayValue();\n })\n .catch((err) => {\n this.setWriteError(err);\n this.field.setErrors({\n write: err.message,\n });\n })\n .finally(() => {\n this.loading = false;\n });\n }\n\n onValueClick($event: CustomEvent) {\n $event.preventDefault();\n if (this.isEditable) {\n this.editMode = true;\n }\n }\n\n async explainPendingCall() {\n await this.showToast('Please reconnect your Tap to save pending changes');\n }\n\n onFocusOut() {\n this.focusOut.emit(this.field.value);\n }\n\n cancelEdit($event?: any) {\n debug(this.config.key, 'cancel edit', $event);\n if (this.globalSubmit) {\n this.restaureValue();\n } else {\n this.writeError = undefined;\n if (this.pendingCallManager.hasPendingCall()) {\n this.pendingCallManager.cancel();\n } else {\n this.editMode = false;\n this.restaureValue();\n }\n }\n }\n\n getFieldValue(): any {\n let fieldValue = this.field.value;\n if (this.config.editFormatter) {\n fieldValue = this.config.editFormatter.read(fieldValue);\n }\n return fieldValue;\n }\n\n async showToast(msg: string, color?: ColorType) {\n const toast = await this.toastController.create({\n message: msg,\n duration: 2000,\n position: 'bottom',\n color: color,\n });\n await toast.present();\n }\n\n refreshValue() {\n return this._execRefreshKey();\n }\n\n private _execRefreshKey() {\n return this.infoResolver.refreshKey(this.config);\n }\n\n restaureValue() {\n this.setFieldValue(this.originalValue);\n this.tapConfigItemState.removePendingChange(this.config);\n }\n\n setFieldValue(\n value: ValueType | undefined,\n options?: {\n emitEvent?: boolean;\n }\n ) {\n const formattedValue = this.config.editFormatter\n ? this.config.editFormatter.write(value)\n : value;\n debug(this.config.key, 'setFieldValue with', formattedValue);\n this.field.setValue(formattedValue, options);\n }\n\n updateDisplayValue() {\n if (this.config.resolvedValue) {\n if (this.config.resolvedValue.error) {\n this.displayedValue = this.config.resolvedValue.error.message;\n } else {\n const value = this.config.resolvedValue.value;\n this.setDisplayValue(value);\n this.setFieldValue(value);\n }\n } else {\n debug(\n this.config.key,\n 'Value is not loaded yet',\n this.config.resolvedValue\n );\n this.displayedValue = undefined;\n }\n }\n\n setDisplayValue(value: ValueType): string {\n if (value instanceof Error) {\n return value.message;\n } else {\n let stringValue: string;\n if (this.config.viewFormatter) {\n stringValue = this.config.viewFormatter(value);\n } else if (typeof value === 'object') {\n stringValue = JSON.stringify(value);\n } else if (this.config.input?.type === 'select') {\n stringValue =\n this.getSelectedOption(value)?.text ||\n (value !== undefined ? (value as any).toString() : '');\n } else {\n stringValue = value !== undefined ? (value as any).toString() : '';\n }\n // debug(`${this.config.key} setDisplayValue with`, stringValue);\n this.displayedValue = stringValue;\n return stringValue;\n }\n }\n\n getSelectedOption(value: ValueType) {\n return this.config.input?.options?.find((option) => option.key === value);\n }\n\n get selectedOption() {\n return this.getSelectedOption(this.config.resolvedValue?.value);\n }\n\n async fetchValue() {\n if (this.config.getValue) {\n this.infoResolver.refreshKey(this.config);\n const result = await this.tapInfoCacheService\n .infoChange(this.config)\n .pipe(\n filter((v) => !!v.loadDate),\n first()\n )\n .toPromise();\n }\n }\n}\n","<ion-item *ngIf=\"_config\" [lines]=\"isEditable ? '' : 'none'\">\n <ion-skeleton-text\n animated\n *ngIf=\"\n displayedValue === undefined && readError === undefined;\n else valueFetchedTemplate\n \"\n ></ion-skeleton-text>\n <ng-template #valueFetchedTemplate>\n <ng-container\n *ngIf=\"ressourceAvailable; else ressourceNotAvailableTemplate\"\n >\n <ng-container *ngIf=\"!editMode || !isEditable; else editModeTemplate\">\n <ion-text\n color=\"warning\"\n class=\"read-value-error\"\n (click)=\"onValueClick($event)\"\n [class]=\"isEditable ? 'cell' : ''\"\n *ngIf=\"readError; else noReadErrorTemplate\"\n >\n <ion-icon name=\"alert-circle\"></ion-icon>\n {{ readError | tapRequestErrorToString }}\n </ion-text>\n <ng-template #noReadErrorTemplate>\n <ng-container [ngSwitch]=\"inputOptions.type\">\n <ng-container *ngSwitchCase=\"'toggle'\">\n <ion-toggle [disabled]=\"true\" [checked]=\"field.value\"></ion-toggle\n ></ng-container>\n <div\n (click)=\"onValueClick($event)\"\n *ngSwitchCase=\"'select'\"\n class=\"item-value\"\n [class]=\"isEditable ? 'cell' : ''\"\n >\n <ion-icon\n *ngIf=\"selectedOption?.icon\"\n [name]=\"selectedOption?.icon\"\n ></ion-icon>\n <ion-text class=\"tap-config-item-value\">\n {{ displayedValue }}\n </ion-text>\n </div>\n <ion-text\n (click)=\"onValueClick($event)\"\n class=\"item-value tap-config-item-value\"\n *ngSwitchDefault\n [class]=\"isEditable ? 'cell' : ''\"\n >\n {{ displayedValue }}\n </ion-text>\n </ng-container>\n </ng-template>\n </ng-container>\n <ng-template #editModeTemplate>\n <ng-container [ngSwitch]=\"inputOptions.type\">\n <ion-toggle\n *ngSwitchCase=\"'toggle'\"\n [disabled]=\"loading || !!pendingEditCall\"\n [formControl]=\"field\"\n ></ion-toggle>\n <ion-select\n style=\"max-width: 100% !important\"\n *ngSwitchCase=\"'select'\"\n [formControl]=\"field\"\n [multiple]=\"inputOptions.multiple\"\n >\n <ion-select-option\n *ngFor=\"let option of inputOptions.options\"\n [value]=\"option.key\"\n >\n <ion-icon *ngIf=\"option.icon\" [name]=\"option.icon\"></ion-icon>\n {{ option.text }}\n </ion-select-option>\n <ion-select-option\n *ngFor=\"let enumValue of inputOptions.enum?.data | listEnumValues\"\n [value]=\"enumValue\"\n >\n <ion-icon\n [name]=\"\n enumValue\n | tapResourceEnumTranslate : 'icon' : inputOptions.enum\n | async\n \"\n ></ion-icon>\n {{\n enumValue\n | tapResourceEnumTranslate : 'title' : inputOptions.enum\n | async\n }}\n </ion-select-option>\n </ion-select>\n <ion-input\n *ngSwitchDefault\n (keydown.enter)=\"submit($event)\"\n (keydown.escape)=\"cancelEdit($event)\"\n [disabled]=\"!!pendingEditCall\"\n [maxlength]=\"inputOptions.maxLength\"\n [minlength]=\"inputOptions.minLength\"\n [max]=\"inputOptions.max\"\n [min]=\"inputOptions.min\"\n [pattern]=\"inputOptions.pattern\"\n [placeholder]=\"placeholder\"\n [type]=\"inputOptions.type\"\n appAutofocus\n class=\"cellInput\"\n [formControl]=\"field\"\n >\n </ion-input>\n </ng-container>\n </ng-template>\n <ion-buttons slot=\"end\">\n <ion-button\n (click)=\"explainPendingCall()\"\n *ngIf=\"pendingEditCall\"\n [disabled]=\"false\"\n color=\"warning\"\n >\n <ion-icon name=\"alert-circle\"></ion-icon>\n </ion-button>\n <ion-button\n (click)=\"editMode = true\"\n *ngIf=\"!globalSubmit && !editMode && info.putValue\"\n >\n <ion-icon name=\"create\"></ion-icon>\n </ion-button>\n <ion-button\n (click)=\"submit($event)\"\n *ngIf=\"!globalSubmit && editMode && !pendingEditCall\"\n [disabled]=\"loading || pendingEditCall\"\n >\n <ion-icon name=\"send\"></ion-icon>\n </ion-button>\n <ion-button\n (click)=\"cancelEdit()\"\n *ngIf=\"isEditable && editMode && (!globalSubmit || isEdited())\"\n color=\"quaternary\"\n [disabled]=\"loading\"\n >\n <ion-icon name=\"close\"></ion-icon>\n </ion-button>\n <ion-button\n (click)=\"refreshValue()\"\n *ngIf=\"refresh && info.getValue && !editMode\"\n [disabled]=\"loading\"\n >\n <ion-icon name=\"sync\"></ion-icon>\n </ion-button>\n </ion-buttons>\n <ion-spinner *ngIf=\"loading\" slot=\"end\"></ion-spinner>\n </ng-container>\n <ng-template #ressourceNotAvailableTemplate>\n <ion-text color=\"warning\" class=\"read-value-error\">\n <ion-icon name=\"alert-circle\"></ion-icon>\n Not available with your firmware version or Tap model\n </ion-text>\n </ng-template>\n </ng-template>\n</ion-item>\n<ion-item *ngIf=\"writeError\">\n <ion-text color=\"danger\" class=\"tap-config-item-error\">\n <ion-icon name=\"alert-circle\"></ion-icon>\n Failed to write value: {{ writeError | tapRequestErrorToString }}\n </ion-text>\n</ion-item>\n<app-common-form-error-item\n [formControlRef]=\"field\"\n></app-common-form-error-item>\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { TapInfoCacheService } from '@iotize/ionic';\nimport { Observable, of } from 'rxjs';\nimport { TapConfigItemStateService } from '../tap-config-item-state.service';\nimport { TapConfigItemExtended } from '../tap-config-item/tap-config-item.definition';\n\n@Pipe({\n name: 'tapConfigItemDisplayed',\n})\nexport class TapConfigItemDisplayedPipe implements PipeTransform {\n constructor(\n public readonly tapInfoCacheService: TapInfoCacheService,\n public readonly tapConfigItemStateService: TapConfigItemStateService\n ) {}\n\n transform(config: TapConfigItemExtended | undefined): Observable<boolean> {\n if (!config || !config.isDisplayed) {\n return of(true);\n }\n return config.isDisplayed(this);\n }\n}\n","import { Component, Input } from '@angular/core';\n\nimport {\n TapConfigItem,\n TapInfoDAOService,\n TapInfoKeyObjectOrString,\n TapResourceKey,\n} from '@iotize/ionic';\nimport { TranslateService } from '@ngx-translate/core';\nimport { defaultInfoResolverConfig } from '../config';\nimport { tapResourceTranslateKey } from '../utility';\n\nimport { TapResourceMetaData } from '@iotize/tap/service/all';\nimport { JSONSchema7 } from 'json-schema';\nimport { AbstractControl, Vali