Browse Source

UI: Remove json form component. Remove all react dependencies.

pull/12325/head
Igor Kulikov 2 years ago
parent
commit
39abbacb20
  1. 2
      ui-ngx/angular.json
  2. 20
      ui-ngx/package.json
  3. 3
      ui-ngx/src/app/core/http/widget.service.ts
  4. 15
      ui-ngx/src/app/core/services/utils.service.ts
  5. 2
      ui-ngx/src/app/modules/common/modules-map.ts
  6. 39
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts
  7. 40
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts
  8. 8
      ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts
  9. 22
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts
  10. 9
      ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts
  11. 2
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config-dialog.component.html
  12. 7
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config-dialog.component.ts
  13. 15
      ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts
  14. 5
      ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts
  15. 11
      ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts
  16. 4
      ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html
  17. 13
      ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts
  18. 13
      ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts
  19. 2
      ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts
  20. 3
      ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.html
  21. 28
      ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts
  22. 13
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  23. 64
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  24. 15
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  25. 70
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  26. 34
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  27. 62
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html
  28. 115
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  29. 18
      ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts
  30. 23
      ui-ngx/src/app/shared/components/json-form/json-form-component.models.ts
  31. 23
      ui-ngx/src/app/shared/components/json-form/json-form.component.html
  32. 20
      ui-ngx/src/app/shared/components/json-form/json-form.component.scss
  33. 293
      ui-ngx/src/app/shared/components/json-form/json-form.component.ts
  34. 239
      ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx
  35. 177
      ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx
  36. 122
      ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx
  37. 46
      ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx
  38. 186
      ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx
  39. 40
      ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx
  40. 83
      ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx
  41. 44
      ui-ngx/src/app/shared/components/json-form/react/json-form-fieldset.tsx
  42. 27
      ui-ngx/src/app/shared/components/json-form/react/json-form-help.tsx
  43. 40
      ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx
  44. 162
      ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx
  45. 108
      ui-ngx/src/app/shared/components/json-form/react/json-form-image.tsx
  46. 40
      ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx
  47. 40
      ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx
  48. 33
      ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx
  49. 99
      ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx
  50. 51
      ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx
  51. 202
      ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx
  52. 53
      ui-ngx/src/app/shared/components/json-form/react/json-form-react.tsx
  53. 217
      ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx
  54. 93
      ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx
  55. 92
      ui-ngx/src/app/shared/components/json-form/react/json-form-text.tsx
  56. 141
      ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts
  57. 361
      ui-ngx/src/app/shared/components/json-form/react/json-form.scss
  58. 46
      ui-ngx/src/app/shared/components/json-form/react/styles/thingsboardTheme.ts
  59. 203
      ui-ngx/src/app/shared/legacy/json-form-utils.ts
  60. 88
      ui-ngx/src/app/shared/legacy/json-form.models.ts
  61. 48
      ui-ngx/src/app/shared/models/dynamic-form.models.ts
  62. 54
      ui-ngx/src/app/shared/models/widget.models.ts
  63. 3
      ui-ngx/src/app/shared/shared.module.ts
  64. 5
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  65. 4
      ui-ngx/src/styles.scss
  66. 2
      ui-ngx/src/tsconfig.app.json
  67. 1
      ui-ngx/tsconfig.json
  68. 730
      ui-ngx/yarn.lock

2
ui-ngx/angular.json

@ -99,8 +99,6 @@
"node_modules/jquery.terminal/css/jquery.terminal.min.css",
"node_modules/tooltipster/dist/css/tooltipster.bundle.min.css",
"node_modules/tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css",
"src/app/shared/components/json-form/react/json-form.scss",
"node_modules/rc-select/assets/index.less",
"node_modules/jstree-bootstrap-theme/dist/themes/proton/style.min.css",
"node_modules/leaflet/dist/leaflet.css",
"src/app/modules/home/components/widget/lib/maps/markers.scss",

20
ui-ngx/package.json

@ -25,8 +25,6 @@
"@angular/platform-browser-dynamic": "18.2.13",
"@angular/router": "18.2.13",
"@auth0/angular-jwt": "^5.2.0",
"@emotion/react": "11.13.3",
"@emotion/styled": "11.13.0",
"@flowjs/flow.js": "^2.14.1",
"@flowjs/ngx-flow": "18.0.1",
"@geoman-io/leaflet-geoman-free": "2.17.0",
@ -34,12 +32,6 @@
"@mat-datetimepicker/core": "~14.0.0",
"@mdi/svg": "^7.4.47",
"@messageformat/core": "^3.4.0",
"@mui/icons-material": "6.1.2",
"@mui/lab": "6.0.0-beta.10",
"@mui/material": "6.1.2",
"@mui/styles": "6.1.2",
"@mui/system": "6.1.2",
"@mui/x-date-pickers": "7.18.0",
"@ngrx/effects": "^18.1.1",
"@ngrx/store": "^18.1.1",
"@ngrx/store-devtools": "^18.1.1",
@ -85,16 +77,8 @@
"ngx-sharebuttons": "^15.0.6",
"ngx-translate-messageformat-compiler": "^7.0.0",
"objectpath": "^2.0.0",
"prettier": "^2.8.3",
"prop-types": "^15.8.1",
"qrcode": "^1.5.4",
"raphael": "^2.3.0",
"rc-select": "14.15.2",
"react": "18.3.1",
"react-ace": "12.0.0",
"react-dom": "18.3.1",
"react-dropzone": "14.2.9",
"reactcss": "^1.2.3",
"rxjs": "~7.8.1",
"schema-inspector": "^2.1.0",
"screenfull": "^6.0.2",
@ -137,8 +121,6 @@
"@types/lodash": "^4.17.13",
"@types/node": "~20.17.8",
"@types/raphael": "^2.3.9",
"@types/react": "18.3.10",
"@types/react-dom": "18.3.0",
"@types/systemjs": "6.15.1",
"@types/tinycolor2": "^1.4.6",
"@types/tooltipster": "^0.0.35",
@ -162,8 +144,6 @@
"typescript": "~5.5.4"
},
"resolutions": {
"@types/react": "18.3.10",
"rc-virtual-list": "3.5.2",
"ace-builds": "1.36.5",
"tinymce": "6.8.5",
"rollup": "4.22.4",

3
ui-ngx/src/app/core/http/widget.service.ts

@ -24,7 +24,7 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import {
BaseWidgetType,
DeprecatedFilter,
fullWidgetTypeFqn,
fullWidgetTypeFqn, migrateWidgetTypeToDynamicForms,
WidgetType,
widgetType,
WidgetTypeDetails,
@ -271,6 +271,7 @@ export class WidgetService {
return this.getWidgetType(templateWidgetType.template.fullFqn,
config).pipe(
map((result) => {
result = migrateWidgetTypeToDynamicForms(result);
const widgetInfo = toWidgetInfo(result);
widgetInfo.fullFqn = undefined;
return widgetInfo;

15
ui-ngx/src/app/core/services/utils.service.ts

@ -23,7 +23,6 @@ import {
baseUrl,
createLabelFromDatasource,
deepClone,
deleteNullProperties,
guid,
hashCode,
isDefined,
@ -41,7 +40,6 @@ import { DataKeyType, SharedTelemetrySubscriber } from '@app/shared/models/telem
import { alarmFields, alarmSeverityTranslations, alarmStatusTranslations } from '@shared/models/alarm.models';
import { materialColors } from '@app/shared/models/material.models';
import { WidgetInfo } from '@home/models/widget-component.models';
import jsonSchemaDefaults from 'json-schema-defaults';
import { Observable } from 'rxjs';
import { publishReplay, refCount } from 'rxjs/operators';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
@ -51,6 +49,7 @@ import { DatePipe, DOCUMENT } from '@angular/common';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
import cssjs from '@core/css/css';
import { isNotEmptyTbFunction } from '@shared/models/js-function.models';
import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models';
const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g');
@ -138,10 +137,10 @@ export class UtilsService {
return predefinedFunctions[func];
}
public getDefaultDatasource(dataKeySchema: any): Datasource {
public getDefaultDatasource(dataKeyForm: FormProperty[]): Datasource {
const datasource = deepClone(this.defaultDatasource);
if (isDefined(dataKeySchema)) {
datasource.dataKeys[0].settings = this.generateObjectFromJsonSchema(dataKeySchema);
if (dataKeyForm?.length) {
datasource.dataKeys[0].settings = defaultFormProperties(dataKeyForm);
}
return datasource;
}
@ -189,12 +188,6 @@ export class UtilsService {
return '';
}
public generateObjectFromJsonSchema(schema: any): any {
const obj = jsonSchemaDefaults(schema);
deleteNullProperties(obj);
return obj;
}
public processWidgetException(exception: any): ExceptionData {
const data = this.parseException(exception, -6);
if (data.message?.startsWith('NG0')) {

2
ui-ngx/src/app/modules/common/modules-map.ts

@ -160,7 +160,6 @@ import * as MaterialIconsDialogComponent from '@shared/components/dialog/materia
import * as ColorInputComponent from '@shared/components/color-input.component';
import * as MaterialIconSelectComponent from '@shared/components/material-icon-select.component';
import * as NodeScriptTestDialogComponent from '@shared/components/dialog/node-script-test-dialog.component';
import * as JsonFormComponent from '@shared/components/json-form/json-form.component';
import * as NotificationComponent from '@shared/components/notification/notification.component';
import * as TemplateAutocompleteComponent from '@shared/components/notification/template-autocomplete.component';
import * as ImageInputComponent from '@shared/components/image-input.component';
@ -507,7 +506,6 @@ class ModulesMap implements IModulesMap {
'@shared/components/color-input.component': ColorInputComponent,
'@shared/components/material-icon-select.component': MaterialIconSelectComponent,
'@shared/components/dialog/node-script-test-dialog.component': NodeScriptTestDialogComponent,
'@shared/components/json-form/json-form.component': JsonFormComponent,
'@shared/components/notification/notification.component': NotificationComponent,
'@shared/components/notification/template-autocomplete.component': TemplateAutocompleteComponent,
'@shared/components/image-input.component': ImageInputComponent,

39
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts

@ -26,11 +26,10 @@ import { Widget, WidgetConfigMode, widgetTypesData } from '@shared/models/widget
import { Dashboard } from '@app/shared/models/dashboard.models';
import { IAliasController, IStateController } from '@core/api/widget-api.models';
import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models';
import { isDefined, isDefinedAndNotNull, isString } from '@core/utils';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
export interface AddWidgetDialogData {
dashboard: Dashboard;
@ -102,34 +101,17 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
const widgetInfo = this.data.widgetInfo;
const rawSettingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
const rawLatestDataKeySettingsSchema = widgetInfo.typeLatestDataKeySettingsSchema || widgetInfo.latestDataKeySettingsSchema;
const settingsForm = widgetInfo.typeSettingsForm?.length ?
widgetInfo.typeSettingsForm : (widgetInfo.settingsForm || []);
const dataKeySettingsForm = widgetInfo.typeDataKeySettingsForm?.length ?
widgetInfo.typeDataKeySettingsForm : (widgetInfo.dataKeySettingsForm || []);
const latestDataKeySettingsForm = widgetInfo.typeLatestDataKeySettingsForm?.length ?
widgetInfo.typeLatestDataKeySettingsForm : (widgetInfo.latestDataKeySettingsForm || []);
const typeParameters = widgetInfo.typeParameters;
const dataKeySettingsFunction: DataKeySettingsFunction = typeParameters?.dataKeySettingsFunction;
const actionSources = widgetInfo.actionSources;
const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true;
const rawSettingsForm = widgetInfo.typeSettingsForm || widgetInfo.settingsForm;
const settingsForm = rawSettingsForm?.length ? rawSettingsForm : jsonFormSchemaToFormProperties(rawSettingsSchema);
let settingsSchema;
if (!rawSettingsSchema || rawSettingsSchema === '') {
settingsSchema = {};
} else {
settingsSchema = isString(rawSettingsSchema) ? JSON.parse(rawSettingsSchema) : rawSettingsSchema;
}
let dataKeySettingsSchema;
if (!rawDataKeySettingsSchema || rawDataKeySettingsSchema === '') {
dataKeySettingsSchema = {};
} else {
dataKeySettingsSchema = isString(rawDataKeySettingsSchema) ? JSON.parse(rawDataKeySettingsSchema) : rawDataKeySettingsSchema;
}
let latestDataKeySettingsSchema;
if (!rawLatestDataKeySettingsSchema || rawLatestDataKeySettingsSchema === '') {
latestDataKeySettingsSchema = {};
} else {
latestDataKeySettingsSchema = isString(rawLatestDataKeySettingsSchema) ?
JSON.parse(rawLatestDataKeySettingsSchema) : rawLatestDataKeySettingsSchema;
}
this.widgetConfig = {
widgetName: widgetInfo.widgetName,
config: this.widget.config,
@ -146,9 +128,8 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
actionSources,
isDataEnabled,
settingsForm,
settingsSchema,
dataKeySettingsSchema,
latestDataKeySettingsSchema,
dataKeySettingsForm,
latestDataKeySettingsForm,
dataKeySettingsFunction,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,

40
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts

@ -24,12 +24,11 @@ import { IAliasController, IStateController } from '@core/api/widget-api.models'
import { Widget, WidgetConfigMode } from '@shared/models/widget.models';
import { WidgetComponentService } from '@home/components/widget/widget-component.service';
import { WidgetConfigComponentData } from '../../models/widget-component.models';
import { isDefined, isDefinedAndNotNull, isString } from '@core/utils';
import { isDefined, isDefinedAndNotNull } from '@core/utils';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-edit-widget',
@ -140,34 +139,18 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
return;
}
const widgetInfo = this.widgetComponentService.getInstantWidgetInfo(this.widget);
const rawSettingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema;
const rawDataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema;
const rawLatestDataKeySettingsSchema = widgetInfo.typeLatestDataKeySettingsSchema || widgetInfo.latestDataKeySettingsSchema;
const settingsForm = widgetInfo.typeSettingsForm?.length ?
widgetInfo.typeSettingsForm : (widgetInfo.settingsForm || []);
const dataKeySettingsForm = widgetInfo.typeDataKeySettingsForm?.length ?
widgetInfo.typeDataKeySettingsForm : (widgetInfo.dataKeySettingsForm || []);
const latestDataKeySettingsForm = widgetInfo.typeLatestDataKeySettingsForm?.length ?
widgetInfo.typeLatestDataKeySettingsForm : (widgetInfo.latestDataKeySettingsForm || []);
const typeParameters = widgetInfo.typeParameters;
const dataKeySettingsFunction: DataKeySettingsFunction = typeParameters?.dataKeySettingsFunction;
const actionSources = widgetInfo.actionSources;
const isDataEnabled = isDefined(widgetInfo.typeParameters) ? !widgetInfo.typeParameters.useCustomDatasources : true;
const rawSettingsForm = widgetInfo.typeSettingsForm || widgetInfo.settingsForm;
const settingsForm = rawSettingsForm?.length ? rawSettingsForm : jsonFormSchemaToFormProperties(rawSettingsSchema);
let settingsSchema;
if (!rawSettingsSchema || rawSettingsSchema === '') {
settingsSchema = {};
} else {
settingsSchema = isString(rawSettingsSchema) ? JSON.parse(rawSettingsSchema) : rawSettingsSchema;
}
let dataKeySettingsSchema;
if (!rawDataKeySettingsSchema || rawDataKeySettingsSchema === '') {
dataKeySettingsSchema = {};
} else {
dataKeySettingsSchema = isString(rawDataKeySettingsSchema) ? JSON.parse(rawDataKeySettingsSchema) : rawDataKeySettingsSchema;
}
let latestDataKeySettingsSchema;
if (!rawLatestDataKeySettingsSchema || rawLatestDataKeySettingsSchema === '') {
latestDataKeySettingsSchema = {};
} else {
latestDataKeySettingsSchema = isString(rawLatestDataKeySettingsSchema) ?
JSON.parse(rawLatestDataKeySettingsSchema) : rawLatestDataKeySettingsSchema;
}
this.widgetConfig = {
widgetName: widgetInfo.widgetName,
config: this.widget.config,
@ -177,9 +160,8 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
actionSources,
isDataEnabled,
settingsForm,
settingsSchema,
dataKeySettingsSchema,
latestDataKeySettingsSchema,
dataKeySettingsForm,
latestDataKeySettingsForm,
dataKeySettingsFunction,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,

8
ui-ngx/src/app/modules/home/components/widget/config/basic/cards/aggregated-data-key-row.component.ts

@ -34,7 +34,6 @@ import {
DataKey,
DataKeyConfigMode,
DatasourceType,
JsonSettingsSchema,
Widget,
widgetType
} from '@shared/models/widget.models';
@ -53,6 +52,7 @@ import {
AggregatedValueCardKeySettings
} from '@home/components/widget/lib/cards/aggregated-value-card.models';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-aggregated-data-key-row',
@ -109,8 +109,8 @@ export class AggregatedDataKeyRowComponent implements ControlValueAccessor, OnIn
return this.widgetConfigComponent.widget;
}
get latestDataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsSchema;
get latestDataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsForm;
}
get latestDataKeySettingsDirective(): string {
@ -205,7 +205,7 @@ export class AggregatedDataKeyRowComponent implements ControlValueAccessor, OnIn
data: {
dataKey: deepClone(this.modelValue),
dataKeyConfigMode: DataKeyConfigMode.general,
dataKeySettingsSchema: this.latestDataKeySettingsSchema,
dataKeySettingsForm: this.latestDataKeySettingsForm,
dataKeySettingsDirective: this.latestDataKeySettingsDirective,
dashboard: null,
aliasController: null,

22
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-key-row.component.ts

@ -38,14 +38,7 @@ import {
} from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import {
DataKey,
DataKeyConfigMode,
DatasourceType,
JsonSettingsSchema,
Widget,
widgetType
} from '@shared/models/widget.models';
import { DataKey, DataKeyConfigMode, DatasourceType, Widget, widgetType } from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { merge } from 'rxjs';
@ -66,6 +59,7 @@ import {
TimeSeriesChartYAxisId
} from '@home/components/widget/lib/chart/time-series-chart.models';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
export const dataKeyValid = (key: DataKey): boolean => !!key && !!key.type && !!key.name;
@ -197,16 +191,16 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
return this.widgetConfigComponent.aliasController;
}
get dataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
get dataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.dataKeySettingsForm;
}
get dataKeySettingsDirective(): string {
return this.widgetConfigComponent.modelValue?.dataKeySettingsDirective;
}
get latestDataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsSchema;
get latestDataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsForm;
}
get latestDataKeySettingsDirective(): string {
@ -325,7 +319,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
data: {
dataKey: deepClone(this.modelValue),
dataKeyConfigMode: advanced ? DataKeyConfigMode.advanced : DataKeyConfigMode.general,
dataKeySettingsSchema: this.isLatestDataKeys ? this.latestDataKeySettingsSchema : this.dataKeySettingsSchema,
dataKeySettingsForm: this.isLatestDataKeys ? this.latestDataKeySettingsForm : this.dataKeySettingsForm,
dataKeySettingsDirective: this.isLatestDataKeys ? this.latestDataKeySettingsDirective : this.dataKeySettingsDirective,
dashboard: this.dashboard,
aliasController: this.aliasController,
@ -359,7 +353,7 @@ export class DataKeyRowComponent implements ControlValueAccessor, OnInit, OnChan
}
private _generateDataKey(key: DataKey): DataKey {
key = this.callbacks.generateDataKey(key.name, key.type, this.dataKeySettingsSchema, this.isLatestDataKeys,
key = this.callbacks.generateDataKey(key.name, key.type, this.dataKeySettingsForm, this.isLatestDataKeys,
this.dataKeySettingsFunction);
if (!this.keyRowFormGroup.get('label').value) {
this.keyRowFormGroup.get('label').patchValue(key.label, {emitEvent: false});

9
ui-ngx/src/app/modules/home/components/widget/config/basic/common/data-keys-panel.component.ts

@ -37,7 +37,7 @@ import {
} from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { DataKey, DatasourceType, JsonSettingsSchema, widgetType } from '@shared/models/widget.models';
import { DataKey, DatasourceType, widgetType } from '@shared/models/widget.models';
import { dataKeyRowValidator, dataKeyValid } from '@home/components/widget/config/basic/common/data-key-row.component';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
@ -45,6 +45,7 @@ import { UtilsService } from '@core/services/utils.service';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { TimeSeriesChartYAxisId } from '@home/components/widget/lib/chart/time-series-chart.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-data-keys-panel',
@ -154,8 +155,8 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
this.widgetConfigComponent.modelValue?.typeParameters?.hasAdditionalLatestDataKeys;
}
get datakeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
get dataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.dataKeySettingsForm;
}
get dataKeySettingsFunction(): DataKeySettingsFunction {
@ -276,7 +277,7 @@ export class DataKeysPanelComponent implements ControlValueAccessor, OnInit, OnC
}
addKey() {
const dataKey = this.callbacks.generateDataKey('', null, this.datakeySettingsSchema,
const dataKey = this.callbacks.generateDataKey('', null, this.dataKeySettingsForm,
false, this.dataKeySettingsFunction);
dataKey.label = '';
dataKey.decimals = 0;

2
ui-ngx/src/app/modules/home/components/widget/config/data-key-config-dialog.component.html

@ -35,7 +35,7 @@
<div mat-dialog-content style="padding: 0; height: 800px; overflow: auto;">
<tb-data-key-config #dataKeyConfig
[dataKeyConfigMode]="dataKeyConfigMode"
[dataKeySettingsSchema]="data.dataKeySettingsSchema"
[dataKeySettingsForm]="data.dataKeySettingsForm"
[dataKeySettingsDirective]="data.dataKeySettingsDirective"
[deviceId]="data.deviceId"
[entityAliasId]="data.entityAliasId"

7
ui-ngx/src/app/modules/home/components/widget/config/data-key-config-dialog.component.ts

@ -36,11 +36,12 @@ import { IAliasController } from '@core/api/widget-api.models';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
import { TranslateService } from '@ngx-translate/core';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
export interface DataKeyConfigDialogData {
dataKey: DataKey;
dataKeyConfigMode?: DataKeyConfigMode;
dataKeySettingsSchema: any;
dataKeySettingsForm: FormProperty[];
dataKeySettingsDirective: string;
dashboard: Dashboard;
aliasController: IAliasController;
@ -92,8 +93,8 @@ export class DataKeyConfigDialogComponent extends DialogComponent<DataKeyConfigD
this.dataKeyFormGroup = this.fb.group({
dataKey: [this.data.dataKey, [Validators.required]]
});
if (this.data.dataKeySettingsSchema && this.data.dataKeySettingsSchema.schema ||
this.data.dataKeySettingsDirective && this.data.dataKeySettingsDirective.length) {
if (this.data.dataKeySettingsForm?.length ||
this.data.dataKeySettingsDirective?.length) {
this.hasAdvanced = true;
this.dataKeyConfigHeaderOptions = [
{

15
ui-ngx/src/app/modules/home/components/widget/config/data-key-config.component.ts

@ -24,6 +24,7 @@ import {
DataKey,
dataKeyAggregationTypeHintTranslationMap,
DataKeyConfigMode,
DynamicFormData,
Widget,
widgetType
} from '@shared/models/widget.models';
@ -47,7 +48,6 @@ import { Observable, of } from 'rxjs';
import { map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators';
import { alarmFields } from '@shared/models/alarm.models';
import { JsFuncComponent } from '@shared/components/js-func.component';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
import { WidgetService } from '@core/http/widget.service';
import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models';
@ -58,6 +58,7 @@ import { WidgetConfigComponentData } from '@home/models/widget-component.models'
import { WidgetComponentService } from '@home/components/widget/widget-component.service';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { isNotEmptyTbFunction, TbFunction } from '@shared/models/js-function.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-data-key-config',
@ -123,7 +124,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
widgetType: widgetType;
@Input()
dataKeySettingsSchema: any;
dataKeySettingsForm: FormProperty[];
@Input()
dataKeySettingsDirective: string;
@ -168,7 +169,7 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
public dataKeySettingsFormGroup: UntypedFormGroup;
private dataKeySettingsData: JsonFormComponentData;
private dataKeySettingsData: DynamicFormData;
private alarmKeys: Array<DataKey>;
private functionTypeKeys: Array<DataKey>;
@ -226,15 +227,11 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
type: DataKeyType.function
});
}
if (this.dataKeySettingsSchema && this.dataKeySettingsSchema.schema ||
if (this.dataKeySettingsForm?.length ||
this.dataKeySettingsDirective && this.dataKeySettingsDirective.length) {
this.hasAdvanced = true;
this.dataKeySettingsData = {
schema: this.dataKeySettingsSchema?.schema || {
type: 'object',
properties: {}
},
form: this.dataKeySettingsSchema?.form || ['*'],
settingsForm: this.dataKeySettingsForm,
settingsDirective: this.dataKeySettingsDirective
};
this.dataKeySettingsFormGroup = this.fb.group({

5
ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.models.ts

@ -15,13 +15,14 @@
///
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKey, JsonSettingsSchema } from '@shared/models/widget.models';
import { DataKey } from '@shared/models/widget.models';
import { Observable } from 'rxjs';
import { FormProperty } from '@shared/models/dynamic-form.models';
export type DataKeySettingsFunction = (key: DataKey, isLatestDataKey: boolean) => any;
export interface DataKeysCallbacks {
generateDataKey: (chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema,
generateDataKey: (chip: any, type: DataKeyType, dataKeySettingsForm: FormProperty[],
isLatestDataKey: boolean, dataKeySettingsFunction: DataKeySettingsFunction) => DataKey;
fetchEntityKeys: (entityAliasId: string, types: Array<DataKeyType>) => Observable<Array<DataKey>>;
fetchEntityKeysForDevice: (deviceId: string, types: Array<DataKeyType>) => Observable<Array<DataKey>>;

11
ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts

@ -50,7 +50,7 @@ import { MatAutocomplete, MatAutocompleteTrigger } from '@angular/material/autoc
import { MatChipGrid, MatChipInputEvent, MatChipRow } from '@angular/material/chips';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { DataKey, DatasourceType, JsonSettingsSchema, Widget, widgetType } from '@shared/models/widget.models';
import { DataKey, DatasourceType, Widget, widgetType } from '@shared/models/widget.models';
import { IAliasController } from '@core/api/widget-api.models';
import { DataKeySettingsFunction } from './data-keys.component.models';
import { alarmFields } from '@shared/models/alarm.models';
@ -72,6 +72,7 @@ import { ColorPickerPanelComponent } from '@shared/components/color-picker/color
import { TbPopoverService } from '@shared/components/popover.service';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormProperty } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-data-keys',
@ -151,7 +152,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
aliasController: IAliasController;
@Input()
datakeySettingsSchema: JsonSettingsSchema;
dataKeySettingsForm: FormProperty[];
@Input()
datakeySettingsFunction: DataKeySettingsFunction;
@ -375,7 +376,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
if (this.widgetType === widgetType.alarm) {
this.keys = this.utils.getDefaultAlarmDataKeys();
} else if (this.isCountDatasource) {
this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.datakeySettingsSchema,
this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.dataKeySettingsForm,
this.latestDataKeys, this.datakeySettingsFunction)];
} else {
this.keys = [];
@ -462,7 +463,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
}
private addFromChipValue(chip: DataKey) {
const key = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema, this.latestDataKeys,
const key = this.callbacks.generateDataKey(chip.name, chip.type, this.dataKeySettingsForm, this.latestDataKeys,
this.datakeySettingsFunction);
this.addKey(key);
}
@ -562,7 +563,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
dataKey: deepClone(key),
dataKeySettingsSchema: this.datakeySettingsSchema,
dataKeySettingsForm: this.dataKeySettingsForm,
dataKeySettingsDirective: this.dataKeySettingsDirective,
dashboard: this.dashboard,
aliasController: this.aliasController,

4
ui-ngx/src/app/modules/home/components/widget/config/datasource.component.html

@ -70,7 +70,7 @@
[optDataKeys]="isDataKeysOptional(datasourceFormGroup.get('type').value)"
[simpleDataKeysLabel]="!hasAdditionalLatestDataKeys"
[aliasController]="aliasController"
[datakeySettingsSchema]="dataKeySettingsSchema"
[dataKeySettingsForm]="dataKeySettingsForm"
[dataKeySettingsDirective]="dataKeySettingsDirective"
[datakeySettingsFunction]="dataKeySettingsFunction"
[dashboard]="dashboard"
@ -86,7 +86,7 @@
latestDataKeys
[optDataKeys]="true"
[aliasController]="aliasController"
[datakeySettingsSchema]="latestDataKeySettingsSchema"
[dataKeySettingsForm]="latestDataKeySettingsForm"
[dataKeySettingsDirective]="latestDataKeySettingsDirective"
[datakeySettingsFunction]="dataKeySettingsFunction"
[dashboard]="dashboard"

13
ui-ngx/src/app/modules/home/components/widget/config/datasource.component.ts

@ -29,8 +29,8 @@ import {
Datasource,
DatasourceType,
datasourceTypeTranslationMap,
JsonSettingsSchema,
Widget, WidgetConfigMode,
Widget,
WidgetConfigMode,
widgetType
} from '@shared/models/widget.models';
import { AlarmSearchStatus } from '@shared/models/alarm.models';
@ -43,6 +43,7 @@ import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/wid
import { EntityType } from '@shared/models/entity-type.models';
import { DatasourcesComponent } from '@home/components/widget/config/datasources.component';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-datasource',
@ -108,16 +109,16 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida
return this.widgetConfigComponent.modelValue?.typeParameters?.maxDataKeys;
}
public get dataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
public get dataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.dataKeySettingsForm;
}
public get dataKeySettingsDirective(): string {
return this.widgetConfigComponent.modelValue?.dataKeySettingsDirective;
}
public get latestDataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsSchema;
public get latestDataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.latestDataKeySettingsForm;
}
public get latestDataKeySettingsDirective(): string {

13
ui-ngx/src/app/modules/home/components/widget/config/datasources.component.ts

@ -30,8 +30,8 @@ import {
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import {
Datasource,
DatasourceType, datasourceValid,
JsonSettingsSchema,
DatasourceType,
datasourceValid,
WidgetConfigMode,
widgetType
} from '@shared/models/widget.models';
@ -42,6 +42,7 @@ import { UtilsService } from '@core/services/utils.service';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion';
import { FormProperty } from '@shared/models/dynamic-form.models';
@Component({
selector: 'tb-datasources',
@ -336,8 +337,8 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
public addDatasource(emitEvent = true) {
let newDatasource: Datasource;
if (this.widgetConfigComponent.functionsOnly) {
newDatasource = deepClone(this.utils.getDefaultDatasource(this.dataKeySettingsSchema.schema));
newDatasource.dataKeys = [this.dataKeysCallbacks.generateDataKey('Sin', DataKeyType.function, this.dataKeySettingsSchema,
newDatasource = deepClone(this.utils.getDefaultDatasource(this.dataKeySettingsForm));
newDatasource.dataKeys = [this.dataKeysCallbacks.generateDataKey('Sin', DataKeyType.function, this.dataKeySettingsForm,
false, this.dataKeySettingsFunction)];
} else {
const type = this.basicMode ? this.datasourcesMode : DatasourceType.entity;
@ -351,8 +352,8 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
this.datasourcesFormArray.push(this.fb.control(newDatasource, []), {emitEvent});
}
private get dataKeySettingsSchema(): JsonSettingsSchema {
return this.widgetConfigComponent.modelValue?.dataKeySettingsSchema;
private get dataKeySettingsForm(): FormProperty[] {
return this.widgetConfigComponent.modelValue?.dataKeySettingsForm;
}
private get dataKeySettingsFunction(): DataKeySettingsFunction {

2
ui-ngx/src/app/modules/home/components/widget/config/widget-config.component.models.ts

@ -202,7 +202,7 @@ export abstract class BasicWidgetConfigComponent extends PageComponent implement
protected constructDataKey(configData: WidgetConfigComponentData, key: DataKey, isLatestKey: boolean): DataKey {
const dataKey =
this.widgetConfigComponent.widgetConfigCallbacks.generateDataKey(key.name, key.type,
configData.dataKeySettingsSchema, isLatestKey, configData.dataKeySettingsFunction);
configData.dataKeySettingsForm, isLatestKey, configData.dataKeySettingsFunction);
if (key.label) {
dataKey.label = key.label;
}

3
ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.html

@ -18,9 +18,6 @@
<div [formGroup]="widgetSettingsFormGroup">
<ng-container #definedSettingsContent></ng-container>
<div class="tb-settings-directive-error" *ngIf="definedDirectiveError">{{definedDirectiveError}}</div>
<tb-json-form *ngIf="useJsonForm()"
formControlName="settings">
</tb-json-form>
<tb-dynamic-form *ngIf="useDynamicForm()"
[properties]="settingsForm"
formControlName="settings">

28
ui-ngx/src/app/modules/home/components/widget/config/widget-settings.component.ts

@ -38,8 +38,6 @@ import {
} from '@angular/forms';
import { Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { JsonFormComponent } from '@shared/components/json-form/json-form.component';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
import { DynamicFormData, IWidgetSettingsComponent, Widget, WidgetSettings } from '@shared/models/widget.models';
import { widgetSettingsComponentsMap } from '@home/components/widget/lib/settings/widget-settings.module';
import { Dashboard } from '@shared/models/dashboard.models';
@ -99,7 +97,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy,
private definedSettingsComponentRef: ComponentRef<IWidgetSettingsComponent>;
private definedSettingsComponent: IWidgetSettingsComponent;
private widgetSettingsFormData: JsonFormComponentData | DynamicFormData;
private widgetSettingsFormData: DynamicFormData;
private propagateChange = (_v: any) => { };
constructor(private translate: TranslateService,
@ -166,13 +164,9 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy,
}
}
writeValue(value: JsonFormComponentData | DynamicFormData): void {
writeValue(value: DynamicFormData): void {
this.widgetSettingsFormData = value;
if ('settingsForm' in this.widgetSettingsFormData) {
this.settingsForm = this.widgetSettingsFormData.settingsForm;
} else {
this.settingsForm = null;
}
this.settingsForm = this.widgetSettingsFormData.settingsForm;
if (this.changeSubscription) {
this.changeSubscription.unsubscribe();
this.changeSubscription = null;
@ -187,12 +181,10 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy,
this.updateModel(settings);
});
} else {
const settingsValue = this.useJsonForm() ? this.widgetSettingsFormData : this.widgetSettingsFormData.model;
this.widgetSettingsFormGroup.get('settings').patchValue(settingsValue, {emitEvent: false});
this.widgetSettingsFormGroup.get('settings').patchValue(this.widgetSettingsFormData.model, {emitEvent: false});
this.changeSubscription = this.widgetSettingsFormGroup.get('settings').valueChanges.subscribe(
(data: JsonFormComponentData | WidgetSettings) => {
const settings = this.useJsonForm() ? data.model : data;
this.updateModel(settings);
(data: WidgetSettings) => {
this.updateModel(data);
}
);
}
@ -203,12 +195,8 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy,
this.settingsDirective.length && !this.definedDirectiveError;
}
useJsonForm(): boolean {
return (!this.settingsDirective || !this.settingsDirective.length) && !this.settingsForm?.length;
}
useDynamicForm(): boolean {
return (!this.settingsDirective || !this.settingsDirective.length) && !!this.settingsForm?.length;
return !this.settingsDirective || !this.settingsDirective.length;
}
private updateModel(settings: WidgetSettings) {
@ -258,7 +246,7 @@ export class WidgetSettingsComponent implements ControlValueAccessor, OnDestroy,
}
};
}
} else if (this.useJsonForm() || this.useDynamicForm()) {
} else if (this.useDynamicForm()) {
if (!this.widgetSettingsFormGroup.get('settings').valid) {
return {
widgetSettings: {

13
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -19,23 +19,18 @@ import LeafletMap from './leaflet-map';
import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getDefCenterPosition, parseWithTranslation } from './common-maps-utils';
import {
Datasource,
DatasourceData,
FormattedData,
JsonSettingsSchema,
WidgetActionDescriptor
} from '@shared/models/widget.models';
import { Datasource, DatasourceData, FormattedData, WidgetActionDescriptor } from '@shared/models/widget.models';
import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service';
import { EntityDataPageLink } from '@shared/models/query/query.models';
import { providerClass } from '@home/components/widget/lib/maps/providers/public-api';
import { isDefined, isDefinedAndNotNull, parseFunction, parseTbFunction } from '@core/utils';
import { isDefined, isDefinedAndNotNull, parseTbFunction } from '@core/utils';
import L from 'leaflet';
import { firstValueFrom, forkJoin, from, Observable, of } from 'rxjs';
import { AttributeService } from '@core/http/attribute.service';
import { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
// @dynamic
export class MapWidgetController implements MapWidgetInterface {
@ -113,7 +108,7 @@ export class MapWidgetController implements MapWidgetInterface {
map: LeafletMap;
provider: MapProviders;
schema: JsonSettingsSchema;
form: FormProperty[];
data: DatasourceData[];
settings: WidgetUnitedMapSettings;
pageLink: EntityDataPageLink;

64
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -36,7 +36,7 @@ import {
ResourcesService
} from '@core/services/resources.service';
import {
IWidgetSettingsComponent,
IWidgetSettingsComponent, migrateWidgetTypeToDynamicForms,
Widget,
widgetActionSources,
WidgetControllerDescriptor,
@ -63,6 +63,7 @@ import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/b
import { IBasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { compileTbFunction, TbFunction } from '@shared/models/js-function.models';
import { HttpClient } from '@angular/common/http';
import { jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
@Injectable()
export class WidgetComponentService {
@ -113,9 +114,8 @@ export class WidgetComponentService {
templateCss: this.utils.editWidgetInfo.templateCss,
controllerScript: this.utils.editWidgetInfo.controllerScript,
settingsForm: this.utils.editWidgetInfo.settingsForm,
settingsSchema: this.utils.editWidgetInfo.settingsSchema,
dataKeySettingsSchema: this.utils.editWidgetInfo.dataKeySettingsSchema,
latestDataKeySettingsSchema: this.utils.editWidgetInfo.latestDataKeySettingsSchema,
dataKeySettingsForm: this.utils.editWidgetInfo.dataKeySettingsForm,
latestDataKeySettingsForm: this.utils.editWidgetInfo.latestDataKeySettingsForm,
settingsDirective: this.utils.editWidgetInfo.settingsDirective,
dataKeySettingsDirective: this.utils.editWidgetInfo.dataKeySettingsDirective,
latestDataKeySettingsDirective: this.utils.editWidgetInfo.latestDataKeySettingsDirective,
@ -276,6 +276,7 @@ export class WidgetComponentService {
this.widgetsInfoFetchQueue.set(fullFqn, fetchQueue);
this.widgetService.getWidgetType(fullFqn, {ignoreErrors: true}).subscribe(
(widgetType) => {
widgetType = migrateWidgetTypeToDynamicForms(widgetType);
this.loadWidget(widgetType, widgetInfoSubject);
},
() => {
@ -302,14 +303,11 @@ export class WidgetComponentService {
if (widgetControllerDescriptor.settingsForm) {
widgetInfo.typeSettingsForm = widgetControllerDescriptor.settingsForm;
}
if (widgetControllerDescriptor.settingsSchema) {
widgetInfo.typeSettingsSchema = widgetControllerDescriptor.settingsSchema;
if (widgetControllerDescriptor.dataKeySettingsForm) {
widgetInfo.typeDataKeySettingsForm = widgetControllerDescriptor.dataKeySettingsForm;
}
if (widgetControllerDescriptor.dataKeySettingsSchema) {
widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema;
}
if (widgetControllerDescriptor.latestDataKeySettingsSchema) {
widgetInfo.typeLatestDataKeySettingsSchema = widgetControllerDescriptor.latestDataKeySettingsSchema;
if (widgetControllerDescriptor.latestDataKeySettingsForm) {
widgetInfo.typeLatestDataKeySettingsForm = widgetControllerDescriptor.latestDataKeySettingsForm;
}
widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters;
widgetInfo.actionSources = widgetControllerDescriptor.actionSources;
@ -510,12 +508,23 @@ export class WidgetComponentService {
' }\n\n' +
' self.getSettingsSchema = function() {\n\n' +
' self.getSettingsForm = function() {\n\n' +
return [
{
'id': 'testProp',
'name': 'Test property',
'type': 'text',
'default': 'Default value'
}
];
' }\n\n' +
' self.getDataKeySettingsSchema = function() {\n\n' +
' self.getDataKeySettingsForm = function() {\n\n' +
return [];
' }\n\n' +
' self.getLatestDataKeySettingsForm = function() {\n\n' +
return [];
' }\n\n' +
' self.onDestroy = function() {\n\n' +
@ -546,15 +555,30 @@ export class WidgetComponentService {
if (isFunction(widgetTypeInstance.getSettingsForm)) {
result.settingsForm = widgetTypeInstance.getSettingsForm();
}
if (isFunction(widgetTypeInstance.getSettingsSchema)) {
result.settingsSchema = widgetTypeInstance.getSettingsSchema();
if (isFunction(widgetTypeInstance.getDataKeySettingsForm)) {
result.dataKeySettingsForm = widgetTypeInstance.getDataKeySettingsForm();
}
if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {
result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();
if (isFunction(widgetTypeInstance.getLatestDataKeySettingsForm)) {
result.latestDataKeySettingsForm = widgetTypeInstance.getLatestDataKeySettingsForm();
}
if (isFunction(widgetTypeInstance.getLatestDataKeySettingsSchema)) {
result.latestDataKeySettingsSchema = widgetTypeInstance.getLatestDataKeySettingsSchema();
/** Start migrate from old JSON Schema Form **/
if (isFunction((widgetTypeInstance as any).getSettingsSchema) && !result.settingsForm?.length) {
const settingsSchema = (widgetTypeInstance as any).getSettingsSchema();
result.settingsForm = jsonFormSchemaToFormProperties(settingsSchema);
}
if (isFunction((widgetTypeInstance as any).getDataKeySettingsSchema) && !result.dataKeySettingsForm?.length) {
const dataKeySettingsSchema = (widgetTypeInstance as any).getDataKeySettingsSchema();
result.dataKeySettingsForm = jsonFormSchemaToFormProperties(dataKeySettingsSchema);
}
if (isFunction((widgetTypeInstance as any).getLatestDataKeySettingsSchema) && !result.latestDataKeySettingsForm?.length) {
const latestDataKeySettingsSchema = (widgetTypeInstance as any).getLatestDataKeySettingsSchema();
result.latestDataKeySettingsForm = jsonFormSchemaToFormProperties(latestDataKeySettingsSchema);
}
/** End migrate from old JSON Schema Form **/
if (isFunction(widgetTypeInstance.typeParameters)) {
result.typeParameters = widgetTypeInstance.typeParameters();
} else {

15
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -32,9 +32,6 @@
<div [class.!hidden]="selectedOption !== 'appearance'" class="mat-content">
<ng-container *ngTemplateOutlet="appearance"></ng-container>
</div>
<div [class.!hidden]="selectedOption !== 'oldAppearance'" class="mat-content">
<ng-container *ngTemplateOutlet="oldAppearance"></ng-container>
</div>
<div [class.!hidden]="selectedOption !== 'card'" [formGroup]="widgetSettings" class="mat-content">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-title</div>
@ -313,17 +310,5 @@
</tb-widget-settings>
</div>
</ng-template>
<ng-template #oldAppearance>
<div *ngIf="displayAdvancedAppearance" style="height: 100%;" [formGroup]="oldAdvancedSettings">
<tb-widget-settings
[aliasController]="aliasController"
[callbacks]="widgetConfigCallbacks"
[dashboard]="dashboard"
[widget]="widget"
[widgetConfig]="modelValue"
formControlName="settings">
</tb-widget-settings>
</div>
</ng-template>
</div>

70
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -32,10 +32,8 @@ import {
CellClickColumnInfo,
DataKey,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation, DynamicFormData,
GroupInfo,
JsonSchema,
JsonSettingsSchema,
datasourcesHasOnlyComparisonAggregation,
DynamicFormData,
TargetDevice,
targetDeviceValid,
Widget,
@ -74,7 +72,6 @@ import {
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import { EntityService } from '@core/http/entity.service';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
import { Dashboard } from '@shared/models/dashboard.models';
import { entityFields } from '@shared/models/entity.models';
import { Filter, singleEntityFilterFromDeviceId } from '@shared/models/query/query.models';
@ -84,17 +81,9 @@ import { coerceBoolean } from '@shared/decorators/coercion';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
import { TimewindowConfigData } from '@home/components/widget/config/timewindow-config-panel.component';
import { DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { defaultFormProperties, FormProperty } from '@shared/models/dynamic-form.models';
import Timeout = NodeJS.Timeout;
const emptySettingsSchema: JsonSchema = {
type: 'object',
properties: {}
};
const emptySettingsGroupInfoes: GroupInfo[] = [];
const defaultSettingsForm = [
'*'
];
@Component({
selector: 'tb-widget-config',
templateUrl: './widget-config.component.html',
@ -190,7 +179,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
public widgetSettings: UntypedFormGroup;
public layoutSettings: UntypedFormGroup;
public advancedSettings: UntypedFormGroup;
public oldAdvancedSettings: UntypedFormGroup;
public actionsSettings: UntypedFormGroup;
private createBasicModeComponentTimeout: Timeout;
@ -204,7 +192,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
private widgetSettingsSubscription: Subscription;
private layoutSettingsSubscription: Subscription;
private advancedSettingsSubscription: Subscription;
private oldAdvancedSettingsSubscription: Subscription;
private actionsSettingsSubscription: Subscription;
private defaultConfigFormsType: widgetType;
@ -223,7 +210,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
this.dataSettings = this.fb.group({});
this.targetDeviceSettings = this.fb.group({});
this.advancedSettings = this.fb.group({});
this.oldAdvancedSettings = this.fb.group({});
this.widgetSettings = this.fb.group({
title: [null, []],
titleFont: [null, []],
@ -299,10 +285,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
this.advancedSettingsSubscription.unsubscribe();
this.advancedSettingsSubscription = null;
}
if (this.oldAdvancedSettingsSubscription) {
this.oldAdvancedSettingsSubscription.unsubscribe();
this.oldAdvancedSettingsSubscription = null;
}
if (this.actionsSettingsSubscription) {
this.actionsSettingsSubscription.unsubscribe();
this.actionsSettingsSubscription = null;
@ -325,9 +307,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
this.advancedSettingsSubscription = this.advancedSettings.valueChanges.subscribe(
() => this.updateAdvancedSettings()
);
this.oldAdvancedSettingsSubscription = this.oldAdvancedSettings.valueChanges.subscribe(
() => this.updateOldAdvancedSettings()
);
this.actionsSettingsSubscription = this.actionsSettings.valueChanges.subscribe(
() => this.updateActionSettings()
);
@ -350,12 +329,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
value: 'appearance'
}
);
this.headerOptions.push(
{
name: 'Old appearance',
value: 'oldAppearance'
}
);
}
this.headerOptions.push(
{
@ -409,8 +382,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
this.advancedSettings.addControl('settings',
this.fb.control(null, []));
this.oldAdvancedSettings.addControl('settings',
this.fb.control(null, []));
}
registerOnChange(fn: any): void {
@ -582,7 +553,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
this.updateAdvancedForm(config.settings);
this.updateSchemaFormOld(config.settings);
if (layout) {
this.layoutSettings.patchValue(
@ -664,23 +634,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
this.advancedSettings.patchValue({ settings: dynamicFormData }, {emitEvent: false});
}
private updateSchemaFormOld(settings?: any) {
const widgetSettingsFormData: JsonFormComponentData = {};
if (this.modelValue.settingsSchema && this.modelValue.settingsSchema.schema) {
widgetSettingsFormData.schema = this.modelValue.settingsSchema.schema;
widgetSettingsFormData.form = this.modelValue.settingsSchema.form || deepClone(defaultSettingsForm);
widgetSettingsFormData.groupInfoes = this.modelValue.settingsSchema.groupInfoes;
widgetSettingsFormData.model = settings;
} else {
widgetSettingsFormData.schema = deepClone(emptySettingsSchema);
widgetSettingsFormData.form = deepClone(defaultSettingsForm);
widgetSettingsFormData.groupInfoes = deepClone(emptySettingsGroupInfoes);
widgetSettingsFormData.model = settings || {};
}
widgetSettingsFormData.settingsDirective = this.modelValue.settingsDirective;
this.oldAdvancedSettings.patchValue({ settings: widgetSettingsFormData }, {emitEvent: false});
}
private updateDataSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
@ -732,15 +685,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
}
private updateOldAdvancedSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
this.modelValue.config.settings = this.advancedSettings.get('settings').value?.model;
}
this.propagateChange(this.modelValue);
}
}
private updateActionSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
@ -763,7 +707,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
public get displayAdvancedAppearance(): boolean {
return !!this.modelValue && (!!this.modelValue.settingsSchema && !!this.modelValue.settingsSchema.schema ||
return !!this.modelValue && (!!this.modelValue.settingsForm && !!this.modelValue.settingsForm.length ||
!!this.modelValue.settingsDirective && !!this.modelValue.settingsDirective.length);
}
@ -802,7 +746,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
}
public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema,
public generateDataKey(chip: any, type: DataKeyType, dataKeySettingsForm: FormProperty[],
isLatestDataKey: boolean, dataKeySettingsFunction: DataKeySettingsFunction): DataKey {
if (isObject(chip)) {
(chip as DataKey)._hash = Math.random();
@ -834,8 +778,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
} else if (type === DataKeyType.count) {
result.name = 'count';
}
if (datakeySettingsSchema && isDefined(datakeySettingsSchema.schema)) {
result.settings = this.utils.generateObjectFromJsonSchema(datakeySettingsSchema.schema);
if (dataKeySettingsForm?.length) {
result.settings = defaultFormProperties(dataKeySettingsForm);
} else if (dataKeySettingsFunction) {
const settings = dataKeySettingsFunction(result, isLatestDataKey);
if (settings) {

34
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -21,7 +21,6 @@ import {
DatasourceData,
FormattedData,
fullWidgetTypeFqn,
JsonSettingsSchema,
Widget,
WidgetActionDescriptor,
WidgetActionSource,
@ -562,9 +561,8 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri
deprecated: boolean;
scada: boolean;
typeSettingsForm?: FormProperty[];
typeSettingsSchema?: string | any;
typeDataKeySettingsSchema?: string | any;
typeLatestDataKeySettingsSchema?: string | any;
typeDataKeySettingsForm?: FormProperty[];
typeLatestDataKeySettingsForm?: FormProperty[];
image?: string;
description?: string;
tags?: string[];
@ -580,9 +578,8 @@ export interface WidgetConfigComponentData {
actionSources: {[actionSourceId: string]: WidgetActionSource};
isDataEnabled: boolean;
settingsForm: FormProperty[];
settingsSchema: JsonSettingsSchema;
dataKeySettingsSchema: JsonSettingsSchema;
latestDataKeySettingsSchema: JsonSettingsSchema;
dataKeySettingsForm: FormProperty[];
latestDataKeySettingsForm: FormProperty[];
dataKeySettingsFunction: DataKeySettingsFunction;
settingsDirective: string;
dataKeySettingsDirective: string;
@ -605,8 +602,8 @@ export const MissingWidgetType: WidgetInfo = {
'</div>',
templateCss: '',
controllerScript: 'self.onInit = function() {}',
settingsSchema: '{}\n',
dataKeySettingsSchema: '{}\n',
settingsForm: [],
dataKeySettingsForm: [],
image: null,
description: null,
defaultConfig: '{\n' +
@ -632,8 +629,8 @@ export const ErrorWidgetType: WidgetInfo = {
'</div>',
templateCss: '',
controllerScript: 'self.onInit = function() {}',
settingsSchema: '{}\n',
dataKeySettingsSchema: '{}\n',
settingsForm: [],
dataKeySettingsForm: [],
image: null,
description: null,
defaultConfig: '{\n' +
@ -646,9 +643,8 @@ export const ErrorWidgetType: WidgetInfo = {
export interface WidgetTypeInstance {
getSettingsForm?: () => FormProperty[];
getSettingsSchema?: () => string;
getDataKeySettingsSchema?: () => string;
getLatestDataKeySettingsSchema?: () => string;
getDataKeySettingsForm?: () => FormProperty[];
getLatestDataKeySettingsForm?: () => FormProperty[];
typeParameters?: () => WidgetTypeParameters;
useCustomDatasources?: () => boolean;
actionSources?: () => {[actionSourceId: string]: WidgetActionSource};
@ -675,9 +671,8 @@ export const toWidgetInfo = (widgetTypeEntity: WidgetType): WidgetInfo => ({
templateCss: widgetTypeEntity.descriptor.templateCss,
controllerScript: widgetTypeEntity.descriptor.controllerScript,
settingsForm: widgetTypeEntity.descriptor.settingsForm,
settingsSchema: widgetTypeEntity.descriptor.settingsSchema,
dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,
latestDataKeySettingsSchema: widgetTypeEntity.descriptor.latestDataKeySettingsSchema,
dataKeySettingsForm: widgetTypeEntity.descriptor.dataKeySettingsForm,
latestDataKeySettingsForm: widgetTypeEntity.descriptor.latestDataKeySettingsForm,
settingsDirective: widgetTypeEntity.descriptor.settingsDirective,
dataKeySettingsDirective: widgetTypeEntity.descriptor.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetTypeEntity.descriptor.latestDataKeySettingsDirective,
@ -705,9 +700,8 @@ export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
templateCss: widgetInfo.templateCss,
controllerScript: widgetInfo.controllerScript,
settingsForm: widgetInfo.settingsForm,
settingsSchema: widgetInfo.settingsSchema,
dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema,
latestDataKeySettingsSchema: widgetInfo.latestDataKeySettingsSchema,
dataKeySettingsForm: widgetInfo.dataKeySettingsForm,
latestDataKeySettingsForm: widgetInfo.latestDataKeySettingsForm,
settingsDirective: widgetInfo.settingsDirective,
dataKeySettingsDirective: widgetInfo.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetInfo.latestDataKeySettingsDirective,

62
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html

@ -199,52 +199,26 @@
</tb-dynamic-form-properties>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.settings-schema' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonSettingsFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyJson()">
{{ 'widget.tidy' | translate }}
</button>
<button mat-icon-button class="tb-mat-32"
(click)="jsonSettingsFullscreen = !jsonSettingsFullscreen"
matTooltip="{{(jsonSettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<mat-icon>{{ jsonSettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
</div>
<div #settingsJsonInput></div>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.datakey-settings-schema' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonDataKeySettingsFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyDataKeyJson()">
{{ 'widget.tidy' | translate }}
</button>
<button mat-icon-button class="tb-mat-32"
(click)="jsonDataKeySettingsFullscreen = !jsonDataKeySettingsFullscreen"
matTooltip="{{(jsonDataKeySettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<mat-icon>{{ jsonDataKeySettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
</div>
<div #dataKeySettingsJsonInput></div>
<mat-tab label="{{ 'widget.data-key-settings-form' | translate }}">
<div class="tb-resize-container" style="background-color: #fff;">
<tb-dynamic-form-properties
importExport
[exportFileName]="(widget.widgetName || 'widget') + '-data-key-settings-form'"
[(ngModel)]="widget.dataKeySettingsForm"
(ngModelChange)="dataKeySettingsFormUpdated()"
noBorder fillHeight>
</tb-dynamic-form-properties>
</div>
</mat-tab>
<mat-tab [aria-labelledby]="widget.type !== widgetTypes.timeseries ? 'hidden': ''" label="{{ 'widget.latest-datakey-settings-schema' | translate }}">
<div class="tb-resize-container" tb-fullscreen [fullscreen]="jsonLatestDataKeySettingsFullscreen">
<div class="tb-editor-area-title-panel">
<button mat-button (click)="beautifyLatestDataKeyJson()">
{{ 'widget.tidy' | translate }}
</button>
<button mat-icon-button class="tb-mat-32"
(click)="jsonLatestDataKeySettingsFullscreen = !jsonLatestDataKeySettingsFullscreen"
matTooltip="{{(jsonLatestDataKeySettingsFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"
matTooltipPosition="above">
<mat-icon>{{ jsonLatestDataKeySettingsFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button>
</div>
<div #latestDataKeySettingsJsonInput></div>
<mat-tab [aria-labelledby]="widget.type !== widgetTypes.timeseries ? 'hidden': ''" label="{{ 'widget.latest-data-key-settings-form' | translate }}">
<div class="tb-resize-container" style="background-color: #fff;">
<tb-dynamic-form-properties
importExport
[exportFileName]="(widget.widgetName || 'widget') + '-latest-data-key-settings-form'"
[(ngModel)]="widget.latestDataKeySettingsForm"
(ngModelChange)="latestDataKeySettingsFormUpdated()"
noBorder fillHeight>
</tb-dynamic-form-properties>
</div>
</mat-tab>
<mat-tab label="{{ 'widget.widget-settings' | translate }}">

115
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -32,6 +32,7 @@ import { AppState } from '@core/core.state';
import { WidgetService } from '@core/http/widget.service';
import { detailsToWidgetInfo, WidgetInfo } from '@home/models/widget-component.models';
import {
migrateWidgetTypeToDynamicForms,
TargetDeviceType,
Widget,
WidgetConfig,
@ -71,7 +72,7 @@ import { loadModulesCompleter } from '@shared/models/js-function.models';
import { TbPopoverService } from '@shared/components/popover.service';
import { JsFuncModulesComponent } from '@shared/components/js-func-modules.component';
import { MatIconButton } from '@angular/material/button';
import { formPropertyCompletions, jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
import { formPropertyCompletions } from '@shared/models/dynamic-form.models';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import Timeout = NodeJS.Timeout;
@ -108,15 +109,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
@ViewChild('cssInput', {static: true})
cssInputElmRef: ElementRef;
@ViewChild('settingsJsonInput', {static: true})
settingsJsonInputElmRef: ElementRef;
@ViewChild('dataKeySettingsJsonInput', {static: true})
dataKeySettingsJsonInputElmRef: ElementRef;
@ViewChild('latestDataKeySettingsJsonInput', {static: true})
latestDataKeySettingsJsonInputElmRef: ElementRef;
@ViewChild('javascriptInput', {static: true})
javascriptInputElmRef: ElementRef;
@ -154,9 +146,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
fullscreen = false;
htmlFullscreen = false;
cssFullscreen = false;
jsonSettingsFullscreen = false;
jsonDataKeySettingsFullscreen = false;
jsonLatestDataKeySettingsFullscreen = false;
javascriptFullscreen = false;
iFrameFullscreen = false;
@ -164,9 +153,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
editorsResizeCafs: {[editorId: string]: CancelAnimationFrame} = {};
htmlEditor: Ace.Editor;
cssEditor: Ace.Editor;
jsonSettingsEditor: Ace.Editor;
dataKeyJsonSettingsEditor: Ace.Editor;
latestDataKeyJsonSettingsEditor: Ace.Editor;
jsEditor: Ace.Editor;
private initialCompleters: Ace.Completer[];
aceResize$: ResizeObserver;
@ -227,9 +213,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
const config = JSON.parse(this.widget.defaultConfig);
this.widget.defaultConfig = JSON.stringify(config);
}
if (!this.widget.settingsForm?.length) {
this.widget.settingsForm = jsonFormSchemaToFormProperties(this.widget.settingsSchema);
}
this.origWidget = deepClone(this.widget);
if (!this.widgetTypeDetails) {
this.isDirty = true;
@ -360,45 +343,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
})
));
editorsObservables.push(this.createAceEditor(this.settingsJsonInputElmRef, 'json').pipe(
tap((editor) => {
this.jsonSettingsEditor = editor;
this.jsonSettingsEditor.on('input', () => {
const editorValue = this.jsonSettingsEditor.getValue();
if (this.widget.settingsSchema !== editorValue) {
this.widget.settingsSchema = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.dataKeySettingsJsonInputElmRef, 'json').pipe(
tap((editor) => {
this.dataKeyJsonSettingsEditor = editor;
this.dataKeyJsonSettingsEditor.on('input', () => {
const editorValue = this.dataKeyJsonSettingsEditor.getValue();
if (this.widget.dataKeySettingsSchema !== editorValue) {
this.widget.dataKeySettingsSchema = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.latestDataKeySettingsJsonInputElmRef, 'json').pipe(
tap((editor) => {
this.latestDataKeyJsonSettingsEditor = editor;
this.latestDataKeyJsonSettingsEditor.on('input', () => {
const editorValue = this.latestDataKeyJsonSettingsEditor.getValue();
if (this.widget.latestDataKeySettingsSchema !== editorValue) {
this.widget.latestDataKeySettingsSchema = editorValue;
this.isDirty = true;
}
});
})
));
editorsObservables.push(this.createAceEditor(this.javascriptInputElmRef, 'javascript').pipe(
tap((editor) => {
this.jsEditor = editor;
@ -432,10 +376,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
private setAceEditorValues() {
this.htmlEditor.setValue(this.widget.templateHtml ? this.widget.templateHtml : '', -1);
this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1);
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);
this.latestDataKeyJsonSettingsEditor.setValue(this.widget.latestDataKeySettingsSchema ?
this.widget.latestDataKeySettingsSchema : '', -1);
this.jsEditor.setValue(this.controllerScriptBody ? this.controllerScriptBody : '', -1);
this.updateControllerScriptCompleters();
}
@ -608,7 +548,11 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
}),
catchError((err) => {
if (id && err.status === HttpStatusCode.Conflict) {
return this.widgetService.getWidgetTypeById(id.id);
return this.widgetService.getWidgetTypeById(id.id).pipe(
map((details) => {
return migrateWidgetTypeToDynamicForms(details);
})
);
}
return throwError(() => err);
}),
@ -766,43 +710,6 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
);
}
beautifyJson(): void {
beautifyJs(this.widget.settingsSchema, {indent_size: 4}).subscribe(
(res) => {
if (this.widget.settingsSchema !== res) {
this.isDirty = true;
this.widget.settingsSchema = res;
this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1);
}
}
);
}
beautifyDataKeyJson(): void {
beautifyJs(this.widget.dataKeySettingsSchema, {indent_size: 4}).subscribe(
(res) => {
if (this.widget.dataKeySettingsSchema !== res) {
this.isDirty = true;
this.widget.dataKeySettingsSchema = res;
this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1);
}
}
);
}
beautifyLatestDataKeyJson(): void {
beautifyJs(this.widget.latestDataKeySettingsSchema, {indent_size: 4}).subscribe(
(res) => {
if (this.widget.latestDataKeySettingsSchema !== res) {
this.isDirty = true;
this.widget.latestDataKeySettingsSchema = res;
this.latestDataKeyJsonSettingsEditor.setValue(this.widget.latestDataKeySettingsSchema ?
this.widget.latestDataKeySettingsSchema : '', -1);
}
}
);
}
beautifyJs(): void {
beautifyJs(this.controllerScriptBody, {indent_size: 4, wrap_line_length: 60}).subscribe(
(res) => {
@ -890,6 +797,14 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
this.updateControllerScriptCompleters();
}
dataKeySettingsFormUpdated() {
this.isDirty = true;
}
latestDataKeySettingsFormUpdated() {
this.isDirty = true;
}
editControllerScriptModules($event: Event, button: MatIconButton) {
if ($event) {
$event.stopPropagation();

18
ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts

@ -26,7 +26,12 @@ import { WidgetService } from '@core/http/widget.service';
import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.component';
import { map } from 'rxjs/operators';
import { detailsToWidgetInfo, WidgetInfo } from '@home/models/widget-component.models';
import { widgetType, WidgetTypeDetails, WidgetTypeInfo } from '@app/shared/models/widget.models';
import {
migrateWidgetTypeToDynamicForms,
widgetType,
WidgetTypeDetails,
WidgetTypeInfo
} from '@app/shared/models/widget.models';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { RouterTabsComponent } from '@home/components/router-tabs.component';
import { WidgetTypesTableConfigResolver } from '@home/pages/widget/widget-types-table-config.resolver';
@ -68,10 +73,13 @@ const widgetEditorDataResolver: ResolveFn<WidgetEditorData> = (route: ActivatedR
);
} else {
return inject(WidgetService).getWidgetTypeById(widgetTypeId).pipe(
map((result) => ({
widgetTypeDetails: result,
widget: detailsToWidgetInfo(result)
}))
map((result) => {
result = migrateWidgetTypeToDynamicForms(result);
return {
widgetTypeDetails: result,
widget: detailsToWidgetInfo(result)
};
})
);
}
};

23
ui-ngx/src/app/shared/components/json-form/json-form-component.models.ts

@ -1,23 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { JsonSettingsSchema } from '@shared/models/widget.models';
export interface JsonFormComponentData extends JsonSettingsSchema {
model?: any;
settingsDirective?: string;
}

23
ui-ngx/src/app/shared/components/json-form/json-form.component.html

@ -1,23 +0,0 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-json-form" style="background: #fff;" tb-fullscreen [fullscreenElement]="reactFullscreen"
[fullscreen]="isFullscreen"
(fullscreenChanged)="onFullscreenChanged($event)">
<div #reactRoot></div>
<div class="tb-json-form" #reactFullscreen></div>
</div>

20
ui-ngx/src/app/shared/components/json-form/json-form.component.scss

@ -1,20 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-json-form {
padding: 12px;
padding-bottom: 14px !important;
overflow: auto;
}

293
ui-ngx/src/app/shared/components/json-form/json-form.component.ts

@ -1,293 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
ChangeDetectorRef,
Component,
ElementRef,
forwardRef,
Input,
OnChanges,
OnDestroy,
Renderer2,
SimpleChanges,
ViewChild, ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, UntypedFormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { deepClone, isString, unwrapModule } from '@app/core/utils';
import { JsonFormProps } from './react/json-form.models';
import inspector from 'schema-inspector';
import tinycolor from 'tinycolor2';
import { DialogService } from '@app/core/services/dialog.service';
import JsonFormUtils from './react/json-form-utils';
import { JsonFormComponentData } from './json-form-component.models';
import { GroupInfo } from '@shared/models/widget.models';
import { Observable } from 'rxjs/internal/Observable';
import { forkJoin, from } from 'rxjs';
import { TbPopoverService } from '@shared/components/popover.service';
@Component({
selector: 'tb-json-form',
templateUrl: './json-form.component.html',
styleUrls: ['./json-form.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => JsonFormComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => JsonFormComponent),
multi: true,
}
],
encapsulation: ViewEncapsulation.None
})
export class JsonFormComponent implements ControlValueAccessor, Validator, OnChanges, OnDestroy {
@ViewChild('reactRoot', {static: true})
reactRootElmRef: ElementRef<HTMLElement>;
@ViewChild('reactFullscreen', {static: true})
reactFullscreenElmRef: ElementRef<HTMLElement>;
private readonlyValue: boolean;
get readonly(): boolean {
return this.readonlyValue;
}
@Input()
set required(value: boolean) {
this.readonlyValue = coerceBooleanProperty(value);
}
formProps: JsonFormProps = {
isFullscreen: false,
option: {
formDefaults: {
startEmpty: true
}
},
onModelChange: this.onModelChange.bind(this),
onColorClick: this.onColorClick.bind(this),
onIconClick: this.onIconClick.bind(this),
onToggleFullscreen: this.onToggleFullscreen.bind(this),
onHelpClick: this.onHelpClick.bind(this)
};
data: JsonFormComponentData;
model: any;
schema: any;
form: any;
groupInfoes: GroupInfo[];
isModelValid = true;
isFullscreen = false;
fullscreenFinishFn: (el: Element) => void;
private reactRoot: any;
private propagateChange = null;
private propagateChangePending = false;
private writingValue = false;
private updateViewPending = false;
constructor(public elementRef: ElementRef,
private dialogs: DialogService,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
protected store: Store<AppState>,
private cd: ChangeDetectorRef) {
}
ngOnDestroy(): void {
this.destroyReactSchemaForm();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
if (this.propagateChangePending) {
this.propagateChangePending = false;
setTimeout(() => {
this.propagateChange(this.data);
}, 0);
}
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
}
public validate(c: UntypedFormControl) {
return this.isModelValid ? null : {
modelValid: false
};
}
writeValue(data: JsonFormComponentData): void {
this.writingValue = true;
this.data = data;
this.schema = this.data && this.data.schema ? deepClone(this.data.schema) : {
type: 'object'
};
this.schema.strict = true;
this.form = this.data && this.data.form ? deepClone(this.data.form) : [ '*' ];
this.groupInfoes = this.data && this.data.groupInfoes ? deepClone(this.data.groupInfoes) : [];
this.model = this.data && this.data.model || {};
this.model = inspector.sanitize(this.schema, this.model).data;
this.updateAndRender();
this.isModelValid = this.validateModel();
this.writingValue = false;
if (!this.isModelValid || this.updateViewPending) {
this.updateView();
}
}
updateView() {
if (!this.writingValue) {
this.updateViewPending = false;
if (this.data) {
this.data.model = this.model;
if (this.propagateChange) {
try {
this.propagateChange(this.data);
} catch (e) {
this.propagateChangePending = true;
}
} else {
this.propagateChangePending = true;
}
}
} else {
this.updateViewPending = true;
}
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'readonly') {
this.updateAndRender();
}
}
}
}
private onModelChange(key: (string | number)[], val: any, forceUpdate = false) {
if (isString(val) && val === '') {
val = undefined;
}
if (JsonFormUtils.updateValue(key, this.model, val) || forceUpdate) {
this.isModelValid = this.validateModel();
this.updateView();
}
}
private onColorClick(key: (string | number)[],
val: tinycolor.ColorFormats.RGBA,
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) {
this.dialogs.colorPicker(tinycolor(val).toRgbString()).subscribe((result) => {
if (!result?.canceled && colorSelectedFn) {
colorSelectedFn(tinycolor(result?.color).toRgb());
}
});
}
private onIconClick(key: (string | number)[],
val: string,
iconSelectedFn: (icon: string) => void) {
this.dialogs.materialIconPicker(val).subscribe((result) => {
if (!result?.canceled && iconSelectedFn) {
iconSelectedFn(result?.icon);
}
});
}
private onToggleFullscreen(fullscreenFinishFn?: (el: Element) => void) {
this.isFullscreen = !this.isFullscreen;
this.fullscreenFinishFn = fullscreenFinishFn;
this.cd.markForCheck();
}
onFullscreenChanged(fullscreen: boolean) {
this.formProps.isFullscreen = fullscreen;
this.renderReactSchemaForm(false);
if (this.fullscreenFinishFn) {
this.fullscreenFinishFn(this.reactFullscreenElmRef.nativeElement);
this.fullscreenFinishFn = null;
}
}
private onHelpClick(event: MouseEvent, helpId: string, helpVisibleFn: (visible: boolean) => void, helpReadyFn: (ready: boolean) => void) {
const trigger = event.currentTarget as Element;
this.popoverService.toggleHelpPopover(trigger, this.renderer, this.viewContainerRef, helpId, '', '', null, helpVisibleFn, helpReadyFn);
}
private updateAndRender() {
this.formProps.option.formDefaults.readonly = this.readonly;
this.formProps.schema = this.schema;
this.formProps.form = this.form;
this.formProps.groupInfoes = this.groupInfoes;
this.formProps.model = this.model;
this.renderReactSchemaForm();
}
private renderReactSchemaForm(destroy: boolean = true) {
if (destroy) {
this.destroyReactSchemaForm();
}
// import ReactSchemaForm from './react/json-form-react';
const reactSchemaFormObservables: Observable<any>[] = [
from(import('react')),
from(import('react-dom')),
from(import('react-dom/client')),
from(import('./react/json-form-react'))
];
forkJoin(reactSchemaFormObservables).subscribe(
(modules) => {
const react = unwrapModule(modules[0]);
const reactDomClient = unwrapModule(modules[2]);
const jsonFormReact = unwrapModule(modules[3]);
this.reactRoot = reactDomClient.createRoot(this.reactRootElmRef.nativeElement);
this.reactRoot.render(react.createElement(jsonFormReact, this.formProps));
}
);
}
private destroyReactSchemaForm() {
this.reactRoot?.unmount();
}
private validateModel(): boolean {
if (this.schema && this.model) {
return JsonFormUtils.validateBySchema(this.schema, this.model).valid;
}
return true;
}
}

239
ui-ngx/src/app/shared/components/json-form/react/json-form-ace-editor.tsx

@ -1,239 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ThingsboardBaseComponent from './json-form-base-component';
import reactCSS from 'reactcss';
import Button from '@mui/material/Button';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { IEditorProps } from 'react-ace/src/types';
import { map, mergeMap } from 'rxjs/operators';
import { getAce } from '@shared/models/ace/ace.models';
import { from, lastValueFrom } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { CircularProgress, IconButton } from '@mui/material';
import { MouseEvent } from 'react';
import { Help, HelpOutline } from '@mui/icons-material';
import { unwrapModule } from '@core/utils';
const ReactAce = React.lazy(() => {
return lastValueFrom(getAce().pipe(
mergeMap(() => {
return from(import('react-ace')).pipe(
map((module) => unwrapModule(module)
));
})
));
});
interface ThingsboardAceEditorProps extends JsonFormFieldProps {
mode: string;
onTidy: (value: string) => Observable<string>;
}
interface ThingsboardAceEditorState extends JsonFormFieldState {
isFull: boolean;
fullscreenContainerElement: Element;
helpVisible: boolean;
helpReady: boolean;
focused: boolean;
}
class ThingsboardAceEditor extends React.Component<ThingsboardAceEditorProps, ThingsboardAceEditorState> {
private aceEditor: IEditorProps;
constructor(props: ThingsboardAceEditorProps) {
super(props);
this.onValueChanged = this.onValueChanged.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onTidy = this.onTidy.bind(this);
this.onHelp = this.onHelp.bind(this);
this.onLoad = this.onLoad.bind(this);
this.onToggleFull = this.onToggleFull.bind(this);
const value = props.value ? props.value + '' : '';
this.state = {
isFull: false,
fullscreenContainerElement: null,
helpVisible: false,
helpReady: true,
value,
focused: false
};
}
onValueChanged(value) {
this.setState({
value
});
this.props.onChangeValidate({
target: {
value
}
});
}
onBlur() {
this.setState({ focused: false });
}
onFocus() {
this.setState({ focused: true });
}
onTidy() {
if (!this.props.form.readonly) {
const value = this.state.value;
this.props.onTidy(value).subscribe(
(processedValue) => {
this.setState({
value: processedValue
});
this.props.onChangeValidate({
target: {
value: processedValue
}
});
}
);
}
}
onHelp(event: MouseEvent) {
if (this.state.helpVisible && !this.state.helpReady) {
event.preventDefault();
event.stopPropagation();
} else {
this.props.onHelpClick(event, this.props.form.helpId,
(visible) => {
this.setState({
helpVisible: visible
});
}, (ready) => {
this.setState({
helpReady: ready
});
});
}
}
onLoad(editor: IEditorProps) {
this.aceEditor = editor;
}
onToggleFull() {
this.props.onToggleFullscreen((el) => {
this.setState({ isFull: !this.state.isFull, fullscreenContainerElement: el });
});
}
componentDidUpdate() {
}
render() {
const styles = reactCSS({
default: {
tidyButtonStyle: {
color: '#7B7B7B',
minWidth: '32px',
minHeight: '15px',
lineHeight: '15px',
fontSize: '0.800rem',
margin: '0',
padding: '4px',
height: '23px',
borderRadius: '5px',
marginLeft: '5px'
}
}
});
let labelClass = 'tb-label';
if (this.props.form.required) {
labelClass += ' tb-required';
}
if (this.props.form.readonly) {
labelClass += ' tb-readonly';
}
if (this.state.focused) {
labelClass += ' tb-focused';
}
let containerClass = 'tb-container';
const style = this.props.form.style || {width: '100%'};
if (this.state.isFull) {
containerClass += ' fullscreen-form-field';
}
const formDom = (
<div className={containerClass}>
<label className={labelClass}>{this.props.form.title}</label>
<div className='json-form-ace-editor'>
<div className='title-panel'>
<label>{this.props.mode}</label>
{ this.props.onTidy ? <Button style={ styles.tidyButtonStyle }
className='tidy-button' onClick={this.onTidy}>Tidy</Button> : null }
{ this.props.form.helpId ? <div style={ {position: 'relative', display: 'inline-block', marginLeft: '5px'} }>
<IconButton color='primary'
className='help-button' onClick={this.onHelp}>
{this.state.helpVisible ? <Help /> : <HelpOutline /> }
</IconButton>
{ this.state.helpVisible && !this.state.helpReady ?
<div className='tb-absolute-fill help-button-loading'>
<CircularProgress size={18} thickness={4}/>
</div> : null }</div> : null }
<Button style={ styles.tidyButtonStyle }
className='tidy-button' onClick={this.onToggleFull}>
{this.state.isFull ?
'Exit fullscreen' : 'Fullscreen'}
</Button>
</div>
<React.Suspense fallback={<div>Loading...</div>}>
<ReactAce mode={this.props.mode}
theme={'textmate'}
height={this.state.isFull ? '100%' : '150px'}
width={this.state.isFull ? '100%' : '300px'}
onChange={this.onValueChanged}
onFocus={this.onFocus}
onBlur={this.onBlur}
onLoad={this.onLoad}
name={this.props.form.title}
value={this.state.value}
readOnly={this.props.form.readonly}
editorProps={{$blockScrolling: Infinity}}
enableBasicAutocompletion={true}
enableSnippets={true}
enableLiveAutocompletion={true}
style={style}/>
</React.Suspense>
</div>
<div className='json-form-error'
style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>
</div>
);
if (this.state.isFull) {
return ReactDOM.createPortal(formDom, this.state.fullscreenContainerElement);
} else {
return (
<div>
{formDom}
</div>
);
}
}
}
export default ThingsboardBaseComponent(ThingsboardAceEditor);

177
ui-ngx/src/app/shared/components/json-form/react/json-form-array.tsx

@ -1,177 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import JsonFormUtils from './json-form-utils';
import ThingsboardBaseComponent from './json-form-base-component';
import Button from '@mui/material/Button';
import _ from 'lodash';
import IconButton from '@mui/material/IconButton';
import Clear from '@mui/icons-material/Clear';
import Add from '@mui/icons-material/Add';
import Tooltip from '@mui/material/Tooltip';
import {
JsonFormData,
JsonFormFieldProps,
JsonFormFieldState
} from '@shared/components/json-form/react/json-form.models';
interface ThingsboardArrayState extends JsonFormFieldState {
model: any[];
keys: number[];
}
class ThingsboardArray extends React.Component<JsonFormFieldProps, ThingsboardArrayState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onAppend = this.onAppend.bind(this);
this.onDelete = this.onDelete.bind(this);
const model = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model) || [];
const keys: number[] = [];
for (let i = 0; i < model.length; i++) {
keys.push(i);
}
this.state = {
model,
keys
};
}
componentDidMount() {
if (this.props.form.startEmpty !== true && this.state.model.length === 0) {
this.onAppend();
}
}
onAppend() {
let empty;
if (this.props.form && this.props.form.schema && this.props.form.schema.items) {
const items = this.props.form.schema.items;
if (items.type && items.type.indexOf('object') !== -1) {
empty = {};
if (!this.props.options || this.props.options.setSchemaDefaults !== false) {
empty = typeof items.default !== 'undefined' ? items.default : empty;
if (empty) {
JsonFormUtils.traverseSchema(items, (prop, path) => {
if (typeof prop.default !== 'undefined') {
JsonFormUtils.selectOrSet(path, empty, prop.default);
}
});
}
}
} else if (items.type && items.type.indexOf('array') !== -1) {
empty = [];
if (!this.props.options || this.props.options.setSchemaDefaults !== false) {
empty = items.default || empty;
}
} else {
if (!this.props.options || this.props.options.setSchemaDefaults !== false) {
empty = items.default || empty;
}
}
}
const newModel = this.state.model;
newModel.push(empty);
const newKeys = this.state.keys;
let key = 0;
if (newKeys.length > 0) {
key = newKeys[newKeys.length - 1] + 1;
}
newKeys.push(key);
this.setState({
model: newModel,
keys: newKeys
}
);
this.props.onChangeValidate(this.state.model, true);
}
onDelete(index: number) {
const newModel = this.state.model;
newModel.splice(index, 1);
const newKeys = this.state.keys;
newKeys.splice(index, 1);
this.setState(
{
model: newModel,
keys: newKeys
}
);
this.props.onChangeValidate(this.state.model, true);
}
setIndex(index: number) {
return (form: JsonFormData) => {
if (form.key) {
form.key[form.key.indexOf('')] = index;
}
};
}
copyWithIndex(form: JsonFormData, index: number): JsonFormData {
const copy: JsonFormData = _.cloneDeep(form);
copy.arrayIndex = index;
JsonFormUtils.traverseForm(copy, this.setIndex(index));
return copy;
}
render() {
const arrays = [];
const model = this.state.model;
const keys = this.state.keys;
for (let i = 0; i < model.length; i++ ) {
let removeButton: React.JSX.Element = null;
if (!this.props.form.readonly) {
const boundOnDelete = this.onDelete.bind(this, i);
removeButton = <Tooltip title='Remove'><IconButton onClick={boundOnDelete}><Clear/></IconButton></Tooltip>;
}
const forms = (this.props.form.items as JsonFormData[]).map((form, index) => {
const copy = this.copyWithIndex(form, i);
return this.props.builder(copy, this.props.model, index, this.props.onChange,
this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen,
this.props.onHelpClick, this.props.mapper);
});
arrays.push(
<li key={keys[i]} className='list-group-item'>
{removeButton}
{forms}
</li>
);
}
let addButton: JSX.Element = null;
if (!this.props.form.readonly) {
addButton = <Button variant='contained'
color='primary'
startIcon={<Add/>}
style={{marginBottom: '8px'}}
onClick={this.onAppend}>{this.props.form.add || 'New'}</Button>;
}
return (
<div>
<div className='tb-container'>
<div className='tb-head-label'>{this.props.form.title}</div>
<ol className='list-group'>
{arrays}
</ol>
</div>
{addButton}
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardArray);

122
ui-ngx/src/app/shared/components/json-form/react/json-form-base-component.tsx

@ -1,122 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import JsonFormUtils from './json-form-utils';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { isDefinedAndNotNull } from '@core/utils';
export default ThingsboardBaseComponent => class<P extends JsonFormFieldProps>
extends React.Component<P, JsonFormFieldState> {
constructor(props: P) {
super(props);
this.onChangeValidate = this.onChangeValidate.bind(this);
const value = this.defaultValue();
const validationResult = JsonFormUtils.validate(this.props.form, value);
this.state = {
value,
valid: !!(validationResult.valid || !value),
error: !validationResult.valid && value ? validationResult.error.message : null
};
}
componentDidMount() {
if (typeof this.state.value !== 'undefined') {
this.props.onChange(this.props.form.key, this.state.value);
}
}
onChangeValidate(e, forceUpdate?: boolean) {
let value = null;
if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') {
if (!e || e.target?.value === null || e.target?.value === '') {
value = undefined;
} else if (typeof e === 'number') {
value = Number(e);
} else if (e.target.value.indexOf('.') === -1) {
value = parseInt(e.target.value, 10);
} else {
value = parseFloat(e.target.value);
}
} else if (this.props.form.schema.type === 'boolean') {
value = e.target.checked;
} else if (this.props.form.schema.type === 'date' || this.props.form.schema.type === 'array') {
value = e;
} else { // string
value = e.target.value;
}
const validationResult = JsonFormUtils.validate(this.props.form, value);
this.setState({
value,
valid: validationResult.valid,
error: validationResult.valid ? null : validationResult.error.message
});
this.props.onChange(this.props.form.key, value, forceUpdate);
}
defaultValue() {
let value = JsonFormUtils.selectOrSet(this.props.form.key, this.props.model);
if (this.props.form.schema.type === 'boolean') {
if (typeof value !== 'boolean' && typeof this.props.form.default === 'boolean') {
value = this.props.form.default;
}
if (typeof value !== 'boolean' && this.props.form.schema && typeof this.props.form.schema.default === 'boolean') {
value = this.props.form.schema.default;
}
if (typeof value !== 'boolean' &&
this.props.form.schema &&
this.props.form.required) {
value = false;
}
} else if (this.props.form.schema.type === 'integer' || this.props.form.schema.type === 'number') {
if (typeof value !== 'number' && typeof this.props.form.default === 'number') {
value = this.props.form.default;
}
if (typeof value !== 'number' && this.props.form.schema && typeof this.props.form.schema.default === 'number') {
value = this.props.form.schema.default;
}
if (typeof value !== 'number' && this.props.form.titleMap && typeof this.props.form.titleMap[0].value === 'number') {
value = this.props.form.titleMap[0].value;
}
if (value && typeof value === 'string') {
if (value.indexOf('.') === -1) {
value = parseInt(value, 10);
} else {
value = parseFloat(value);
}
}
} else {
if (!value && isDefinedAndNotNull(this.props.form.default)) {
value = this.props.form.default;
}
if (!value && this.props.form.schema && isDefinedAndNotNull(this.props.form.schema.default)) {
value = this.props.form.schema.default;
}
if (!value && this.props.form.titleMap && isDefinedAndNotNull(this.props.form.titleMap[0].value)) {
value = this.props.form.titleMap[0].value;
}
}
return value;
}
render() {
if (this.props.form && this.props.form.schema) {
return <ThingsboardBaseComponent {...this.props} {...this.state} onChangeValidate={this.onChangeValidate}/>;
} else {
return <div></div>;
}
}
};

46
ui-ngx/src/app/shared/components/json-form/react/json-form-checkbox.tsx

@ -1,46 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import Checkbox from '@mui/material/Checkbox';
import { JsonFormFieldProps, JsonFormFieldState } from './json-form.models.js';
import FormControlLabel from '@mui/material/FormControlLabel';
class ThingsboardCheckbox extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
render() {
return (
<div>
<FormControlLabel
control={
<Checkbox
color={'secondary'}
name={this.props.form.key.slice(-1)[0] + ''}
value={this.props.form.key.slice(-1)[0]}
checked={this.props.value || false}
disabled={this.props.form.readonly}
onChange={(e) => {
this.props.onChangeValidate(e);
}}
/>
}
label={this.props.form.title}
/>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardCheckbox);

186
ui-ngx/src/app/shared/components/json-form/react/json-form-color.tsx

@ -1,186 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import reactCSS from 'reactcss';
import tinycolor from 'tinycolor2';
import TextField from '@mui/material/TextField';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import IconButton from '@mui/material/IconButton';
import Clear from '@mui/icons-material/Clear';
import Tooltip from '@mui/material/Tooltip';
interface ThingsboardColorState extends JsonFormFieldState {
color: tinycolor.ColorFormats.RGBA | null;
focused: boolean;
}
class ThingsboardColor extends React.Component<JsonFormFieldProps, ThingsboardColorState> {
containerRef = React.createRef<HTMLDivElement>();
constructor(props: JsonFormFieldProps) {
super(props);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onValueChanged = this.onValueChanged.bind(this);
this.onSwatchClick = this.onSwatchClick.bind(this);
this.onClear = this.onClear.bind(this);
const value = props.value ? props.value + '' : null;
const color = value != null ? tinycolor(value).toRgb() : null;
this.state = {
color,
focused: false
};
}
onBlur() {
this.setState({focused: false});
}
onFocus() {
this.setState({focused: true});
}
componentDidMount() {
const node = this.containerRef.current;
const colContainer = $(node).children('#color-container');
colContainer.click(() => {
if (!this.props.form.readonly) {
this.onSwatchClick();
}
});
}
componentWillUnmount() {
const node = this.containerRef.current;
const colContainer = $(node).children('#color-container');
colContainer.off( 'click' );
}
onValueChanged(value: tinycolor.ColorFormats.RGBA | null) {
let color: tinycolor.Instance = null;
if (value != null) {
color = tinycolor(value);
}
this.setState({
color: value
});
let colorValue = '';
if (color != null && color.getAlpha() !== 1) {
colorValue = color.toRgbString();
} else if (color != null) {
colorValue = color.toHexString();
}
this.props.onChangeValidate({
target: {
value: colorValue
}
});
}
onSwatchClick() {
this.props.onColorClick(this.props.form.key, this.state.color,
(color) => {
this.onValueChanged(color);
}
);
}
onClear(event: React.MouseEvent) {
if (event) {
event.stopPropagation();
}
this.onValueChanged(null);
}
render() {
let background = 'rgba(0,0,0,0)';
if (this.state.color != null) {
background = `rgba(${ this.state.color.r }, ${ this.state.color.g }, ${ this.state.color.b }, ${ this.state.color.a })`;
}
const styles = reactCSS({
default: {
color: {
background: `${ background }`
},
swatch: {
display: 'inline-block',
marginRight: '10px',
marginTop: 'auto',
marginBottom: 'auto',
cursor: 'pointer',
opacity: `${ this.props.form.readonly ? '0.6' : '1' }`
},
swatchText: {
width: '100%'
},
container: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
},
colorContainer: {
display: 'flex',
width: '100%'
}
},
});
let fieldClass = 'tb-field';
if (this.props.form.required) {
fieldClass += ' tb-required';
}
if (this.props.form.readonly) {
fieldClass += ' tb-readonly';
}
if (this.state.focused) {
fieldClass += ' tb-focused';
}
let stringColor = '';
if (this.state.color != null) {
const color = tinycolor(this.state.color);
stringColor = color.toRgbString();
}
return (
<div ref={this.containerRef} style={ styles.container }>
<div id='color-container' style={ styles.colorContainer }>
<div className='tb-color-preview' style={ styles.swatch }>
<div className='tb-color-result' style={ styles.color }/>
</div>
<TextField
variant={'standard'}
className={fieldClass}
label={this.props.form.title}
error={!this.props.valid}
helperText={this.props.valid ? this.props.form.placeholder : this.props.error}
value={stringColor}
disabled={this.props.form.readonly}
onFocus={this.onFocus}
onBlur={this.onBlur}
style={ styles.swatchText }/>
</div>
<Tooltip title='Clear' placement='top'><IconButton onClick={this.onClear}><Clear/></IconButton></Tooltip>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardColor);

40
ui-ngx/src/app/shared/components/json-form/react/json-form-css.tsx

@ -1,40 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyCss } from '@shared/models/beautify.models';
class ThingsboardCss extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyCss = this.onTidyCss.bind(this);
}
onTidyCss(css: string): Observable<string> {
return beautifyCss(css, {indent_size: 4});
}
render() {
return (
<ThingsboardAceEditor {...this.props} mode='css' onTidy={this.onTidyCss} {...this.state}></ThingsboardAceEditor>
);
}
}
export default ThingsboardCss;

83
ui-ngx/src/app/shared/components/json-form/react/json-form-date.tsx

@ -1,83 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import { AdapterMoment } from '@mui/x-date-pickers/AdapterMoment'
import { LocalizationProvider, DatePicker } from '@mui/x-date-pickers';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import moment from 'moment';
interface ThingsboardDateState extends JsonFormFieldState {
currentValue: Date | null;
}
class ThingsboardDate extends React.Component<JsonFormFieldProps, ThingsboardDateState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onDatePicked = this.onDatePicked.bind(this);
let value: Date | null = null;
if (this.props.value && typeof this.props.value === 'number') {
value = new Date(this.props.value);
}
this.state = {
currentValue: value
};
}
onDatePicked(date: moment.Moment | null) {
this.setState({
currentValue: date?.toDate()
});
this.props.onChangeValidate(date ? date.valueOf() : null);
}
render() {
let fieldClass = 'tb-date-field';
if (this.props.form.required) {
fieldClass += ' tb-required';
}
if (this.props.form.readonly) {
fieldClass += ' tb-readonly';
}
return (
<LocalizationProvider dateAdapter={AdapterMoment}>
<div style={{width: '100%', display: 'block'}}>
<DatePicker
slotProps={{
textField: {
variant: 'standard'
}
}}
format='MM/DD/YYYY'
className={fieldClass}
label={this.props.form.title}
value={moment(this.state.currentValue?.valueOf())}
onChange={this.onDatePicked}
disabled={this.props.form.readonly}
sx={this.props.form.style || {width: '100%'}}
/>
</div>
</LocalizationProvider>
);
}
}
export default ThingsboardBaseComponent(ThingsboardDate);

44
ui-ngx/src/app/shared/components/json-form/react/json-form-fieldset.tsx

@ -1,44 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import {
JsonFormData,
JsonFormFieldProps,
JsonFormFieldState
} from '@shared/components/json-form/react/json-form.models';
class ThingsboardFieldSet extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
render() {
const forms = (this.props.form.items as JsonFormData[]).map((form: JsonFormData, index) => {
return this.props.builder(form, this.props.model, index, this.props.onChange,
this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.onHelpClick, this.props.mapper);
});
return (
<div style={{paddingTop: '20px'}}>
<div className='tb-head-label'>
{this.props.form.title}
</div>
<div>
{forms}
</div>
</div>
);
}
}
export default ThingsboardFieldSet;

27
ui-ngx/src/app/shared/components/json-form/react/json-form-help.tsx

@ -1,27 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
class ThingsboardHelp extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
render() {
return (
<div className={this.props.form.htmlClass} dangerouslySetInnerHTML={{__html: this.props.form.description}} ></div>
);
}
}
export default ThingsboardHelp;

40
ui-ngx/src/app/shared/components/json-form/react/json-form-html.tsx

@ -1,40 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyHtml } from '@shared/models/beautify.models';
class ThingsboardHtml extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyHtml = this.onTidyHtml.bind(this);
}
onTidyHtml(html: string): Observable<string> {
return beautifyHtml(html, {indent_size: 4});
}
render() {
return (
<ThingsboardAceEditor {...this.props} mode='html' onTidy={this.onTidyHtml} {...this.state}></ThingsboardAceEditor>
);
}
}
export default ThingsboardHtml;

162
ui-ngx/src/app/shared/components/json-form/react/json-form-icon.tsx

@ -1,162 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { MouseEvent } from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import reactCSS from 'reactcss';
import TextField from '@mui/material/TextField';
import IconButton from '@mui/material/IconButton';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import Clear from '@mui/icons-material/Clear';
import Icon from '@mui/material/Icon';
import Tooltip from '@mui/material/Tooltip';
interface ThingsboardIconState extends JsonFormFieldState {
icon: string | null;
focused: boolean;
}
class ThingsboardIcon extends React.Component<JsonFormFieldProps, ThingsboardIconState> {
containerRef = React.createRef<HTMLDivElement>();
constructor(props: JsonFormFieldProps) {
super(props);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.onValueChanged = this.onValueChanged.bind(this);
this.onIconClick = this.onIconClick.bind(this);
this.onClear = this.onClear.bind(this);
const icon = props.value ? props.value : '';
this.state = {
icon,
focused: false
};
}
onBlur() {
this.setState({focused: false});
}
onFocus() {
this.setState({focused: true});
}
componentDidMount() {
const node = this.containerRef.current;
const iconContainer = $(node).children('#icon-container');
iconContainer.on('click', (event) => {
if (!this.props.form.readonly) {
this.onIconClick(event);
}
});
}
componentWillUnmount() {
const node = this.containerRef.current;
const iconContainer = $(node).children('#icon-container');
iconContainer.off( 'click' );
}
onValueChanged(value: string | null) {
this.setState({
icon: value
});
this.props.onChange(this.props.form.key, value);
}
onIconClick(_event) {
this.props.onIconClick(this.props.form.key, this.state.icon,
(color) => {
this.onValueChanged(color);
}
);
}
onClear(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.onValueChanged('');
}
render() {
const styles = reactCSS({
default: {
container: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
},
icon: {
padding: '12px',
marginRight: '10px',
marginBottom: 'auto',
cursor: 'pointer',
border: 'solid 1px rgba(0, 0, 0, .27)',
borderRadius: '0'
},
iconContainer: {
display: 'flex',
width: '100%'
},
iconText: {
width: '100%'
},
},
});
let fieldClass = 'tb-field';
if (this.props.form.required) {
fieldClass += ' tb-required';
}
if (this.state.focused) {
fieldClass += ' tb-focused';
}
let pickedIcon = 'more_horiz';
let icon = '';
if (this.state.icon !== '') {
pickedIcon = this.state.icon;
icon = this.state.icon;
}
return (
<div ref={this.containerRef} style={ styles.container }>
<div id='icon-container' style={ styles.iconContainer }>
<IconButton style={ styles.icon }>
<Icon>{pickedIcon}</Icon>
</IconButton>
<TextField
variant={'standard'}
className={fieldClass}
label={this.props.form.title}
error={!this.props.valid}
helperText={this.props.valid ? this.props.form.placeholder : this.props.error}
value={icon}
disabled={this.props.form.readonly}
onFocus={this.onFocus}
onBlur={this.onBlur}
style={ styles.iconText } />
</div>
<Tooltip title='Clear' placement='top'><IconButton onClick={this.onClear}><Clear/></IconButton></Tooltip>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardIcon);

108
ui-ngx/src/app/shared/components/json-form/react/json-form-image.tsx

@ -1,108 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { MouseEvent } from 'react';
import Dropzone from 'react-dropzone';
import ThingsboardBaseComponent from './json-form-base-component';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import IconButton from '@mui/material/IconButton';
import Clear from '@mui/icons-material/Clear';
import Tooltip from '@mui/material/Tooltip';
interface ThingsboardImageState extends JsonFormFieldState {
imageUrl: string;
}
class ThingsboardImage extends React.Component<JsonFormFieldProps, ThingsboardImageState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onDrop = this.onDrop.bind(this);
this.onClear = this.onClear.bind(this);
const value = props.value ? props.value + '' : null;
this.state = {
imageUrl: value
};
}
onDrop(acceptedFiles: File[]) {
const reader = new FileReader();
reader.onload = () => {
this.onValueChanged(reader.result as string);
};
reader.readAsDataURL(acceptedFiles[0]);
}
onValueChanged(value: string) {
this.setState({
imageUrl: value
});
this.props.onChangeValidate({
target: {
value
}
});
}
onClear(event: MouseEvent) {
if (event) {
event.stopPropagation();
}
this.onValueChanged('');
}
render() {
let labelClass = 'tb-label';
if (this.props.form.required) {
labelClass += ' tb-required';
}
if (this.props.form.readonly) {
labelClass += ' tb-readonly';
}
let previewComponent: React.JSX.Element;
if (this.state.imageUrl) {
previewComponent = <img className='tb-image-preview' src={this.state.imageUrl} />;
} else {
previewComponent = <div>No image selected</div>;
}
return (
<div className='tb-container'>
<label className={labelClass}>{this.props.form.title}</label>
<div className='tb-image-select-container'>
<div className='tb-image-preview-container'>{previewComponent}</div>
<div className='tb-image-clear-container'>
<Tooltip title='Clear' placement='top'>
<IconButton className='tb-image-clear-btn' onClick={this.onClear}><Clear/></IconButton>
</Tooltip>
</div>
<Dropzone onDrop={this.onDrop}
accept={ {'image/*': []} } multiple={false}>
{({getRootProps, getInputProps}) => (
<div className='tb-dropzone' {...getRootProps()}>
<div>Drop an image or click to select a file to upload.</div>
<input {...getInputProps()} />
</div>
)}
</Dropzone>
</div>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardImage);

40
ui-ngx/src/app/shared/components/json-form/react/json-form-javascript.tsx

@ -1,40 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyJs } from '@shared/models/beautify.models';
class ThingsboardJavaScript extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyJavascript = this.onTidyJavascript.bind(this);
}
onTidyJavascript(javascript: string): Observable<string> {
return beautifyJs(javascript, {indent_size: 4, wrap_line_length: 60});
}
render() {
return (
<ThingsboardAceEditor {...this.props} mode='javascript' onTidy={this.onTidyJavascript} {...this.state}></ThingsboardAceEditor>
);
}
}
export default ThingsboardJavaScript;

40
ui-ngx/src/app/shared/components/json-form/react/json-form-json.tsx

@ -1,40 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { Observable } from 'rxjs/internal/Observable';
import { beautifyJs } from '@shared/models/beautify.models';
class ThingsboardJson extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onTidyJson = this.onTidyJson.bind(this);
}
onTidyJson(json: string): Observable<string> {
return beautifyJs(json, {indent_size: 4});
}
render() {
return (
<ThingsboardAceEditor {...this.props} mode='json' onTidy={this.onTidyJson} {...this.state}></ThingsboardAceEditor>
);
}
}
export default ThingsboardJson;

33
ui-ngx/src/app/shared/components/json-form/react/json-form-markdown.tsx

@ -1,33 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardAceEditor from './json-form-ace-editor';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
class ThingsboardMarkdown extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
constructor(props: JsonFormFieldProps) {
super(props);
}
render() {
return (
<ThingsboardAceEditor {...this.props} mode='markdown' {...this.state}></ThingsboardAceEditor>
);
}
}
export default ThingsboardMarkdown;

99
ui-ngx/src/app/shared/components/json-form/react/json-form-number.tsx

@ -1,99 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import { TextField } from '@mui/material';
import { ChangeEvent } from 'react';
interface ThingsboardNumberState extends JsonFormFieldState {
focused: boolean;
lastSuccessfulValue: number;
}
class ThingsboardNumber extends React.Component<JsonFormFieldProps, ThingsboardNumberState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.preValidationCheck = this.preValidationCheck.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.state = {
lastSuccessfulValue: this.props.value,
focused: false
};
}
isNumeric(n: any) {
return n === null || n === '' || !isNaN(n) && isFinite(n);
}
onBlur() {
this.setState({focused: false});
}
onFocus() {
this.setState({focused: true});
}
preValidationCheck(e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) {
if (this.isNumeric(e.target.value)) {
this.setState({
lastSuccessfulValue: Number(e.target.value)
});
this.props.onChangeValidate(e);
}
}
render() {
let fieldClass = 'tb-field';
if (this.props.form.required) {
fieldClass += ' tb-required';
}
if (this.props.form.readonly) {
fieldClass += ' tb-readonly';
}
if (this.state.focused) {
fieldClass += ' tb-focused';
}
let value = this.state.lastSuccessfulValue;
if (typeof value !== 'undefined') {
value = Number(value);
} else {
value = null;
}
return (
<div>
<TextField
variant={'standard'}
className={fieldClass}
label={this.props.form.title}
type='number'
error={!this.props.valid}
helperText={this.props.valid ? this.props.form.placeholder : this.props.error}
onChange={this.preValidationCheck}
defaultValue={value}
disabled={this.props.form.readonly}
onFocus={this.onFocus}
onBlur={this.onBlur}
style={this.props.form.style || {width: '100%'}}/>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardNumber);

51
ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx

@ -1,51 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import FormControlLabel from '@mui/material/FormControlLabel';
import { FormLabel, Radio, RadioGroup } from '@mui/material';
import FormControl from '@mui/material/FormControl';
import ThingsboardBaseComponent from '@shared/components/json-form/react/json-form-base-component';
class ThingsboardRadios extends React.Component<JsonFormFieldProps, JsonFormFieldState> {
render() {
const items = this.props.form.titleMap.map((item, index) => {
return (
<FormControlLabel value={item.value} control={<Radio color={'secondary'} />} label={item.name} key={index} />
);
});
let row = false;
if (this.props.form.direction === 'row') {
row = true;
}
return (
<FormControl component='fieldset'
className={this.props.form.htmlClass}
disabled={this.props.form.readonly}>
<FormLabel component='legend'>{this.props.form.title}</FormLabel>
<RadioGroup row={row} name={this.props.form.title} value={this.props.value} onChange={(e) => {
this.props.onChangeValidate(e);
}}>
{items}
</RadioGroup>
</FormControl>
);
}
}
export default ThingsboardBaseComponent(ThingsboardRadios);

202
ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx

@ -1,202 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import Select, { Option } from 'rc-select';
import {
JsonFormFieldProps,
JsonFormFieldState,
KeyLabelItem
} from '@shared/components/json-form/react/json-form.models';
import { Mode } from 'rc-select/lib/interface';
import { deepClone } from '@core/utils';
interface ThingsboardRcSelectState extends JsonFormFieldState {
currentValue: KeyLabelItem | KeyLabelItem[];
items: Array<KeyLabelItem>;
focused: boolean;
}
class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, ThingsboardRcSelectState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onSelect = this.onSelect.bind(this);
this.onDeselect = this.onDeselect.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.state = {
currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'),
items: this.props.form.items as KeyLabelItem[],
focused: false
};
}
keyToCurrentValue(key: string | string[], isArray: boolean): KeyLabelItem | KeyLabelItem[] {
let currentValue: KeyLabelItem | KeyLabelItem[] = isArray ? [] : null;
if (isArray) {
const keys = key;
if (keys) {
(keys as string[]).forEach((keyVal) => {
(currentValue as KeyLabelItem[]).push({key: keyVal, label: this.labelFromKey(keyVal)});
});
}
} else {
currentValue = {key: key as string, label: this.labelFromKey(key as string)};
}
return currentValue;
}
labelFromKey(key: string): string {
let label = key || '';
if (key) {
for (const item of this.props.form.items) {
if (item.value === key) {
label = item.label;
break;
}
}
}
return label;
}
arrayValues(items: KeyLabelItem[]): string[] {
const v: string[] = [];
if (items) {
items.forEach(item => {
v.push(item.key);
});
}
return v;
}
keyIndex(values: KeyLabelItem[], key: string): number {
let index = -1;
if (values) {
for (let i = 0; i < values.length; i++) {
if (values[i].key === key) {
index = i;
break;
}
}
}
return index;
}
onSelect(value: KeyLabelItem) {
if (this.props.form.schema.type === 'array') {
const v = this.state.currentValue as KeyLabelItem[];
v.push(this.keyToCurrentValue(value.key, false) as KeyLabelItem);
this.setState({
currentValue: v
});
this.props.onChangeValidate(this.arrayValues(v));
} else {
this.setState({currentValue: this.keyToCurrentValue(value.key, false)});
this.props.onChangeValidate({target: {value: value.key}});
}
}
onDeselect(value: KeyLabelItem) {
if (this.props.form.schema.type === 'array') {
const v = this.state.currentValue as KeyLabelItem[];
const index = this.keyIndex(v, value.key);
if (index > -1) {
v.splice(index, 1);
}
this.setState({
currentValue: v
});
this.props.onChangeValidate(this.arrayValues(v));
}
}
onBlur() {
this.setState({ focused: false });
}
onFocus() {
this.setState({ focused: true });
}
render() {
let options: React.JSX.Element[] = [];
if (this.state.items && this.state.items.length > 0) {
options = this.state.items.map((item) => (
<Option key={item.value} value={item.value}>{item.label}</Option>
));
}
let labelClass = 'tb-label';
if (this.props.form.required) {
labelClass += ' tb-required';
}
if (this.props.form.readonly) {
labelClass += ' tb-readonly';
}
if (this.state.focused) {
labelClass += ' tb-focused';
}
let mode: Mode;
let value = this.state.currentValue;
if (this.props.form.tags || this.props.form.multiple) {
value = deepClone(value);
if (this.props.form.tags) {
mode = 'tags';
} else if (this.props.form.multiple) {
mode = 'multiple';
}
}
const dropdownStyle = {...this.props.form.dropdownStyle, ...{zIndex: 100001}};
let dropdownClassName = 'tb-rc-select-dropdown';
if (this.props.form.dropdownClassName) {
dropdownClassName += ' ' + this.props.form.dropdownClassName;
}
return (
<div className='tb-container'>
<label className={labelClass}>{this.props.form.title}</label>
<Select
className={this.props.form.className}
dropdownClassName={dropdownClassName}
dropdownStyle={dropdownStyle}
allowClear={this.props.form.allowClear}
showSearch={true}
mode={mode}
maxTagTextLength={this.props.form.maxTagTextLength}
disabled={this.props.form.readonly}
optionLabelProp='children'
value={value}
labelInValue={true}
onSelect={this.onSelect}
onDeselect={this.onDeselect}
onFocus={this.onFocus}
onBlur={this.onBlur}
placeholder={this.props.form.placeholder}
style={this.props.form.style || {width: '100%'}}>
{options}
</Select>
<div className='json-form-error'
style={{opacity: this.props.valid ? '0' : '1',
bottom: '-5px'}}>{this.props.error}</div>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardRcSelect);

53
ui-ngx/src/app/shared/components/json-form/react/json-form-react.tsx

@ -1,53 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import thingsboardTheme from './styles/thingsboardTheme';
import ThingsboardSchemaForm from './json-form-schema-form';
import { JsonFormProps } from './json-form.models';
const tbTheme = createTheme(thingsboardTheme);
class ReactSchemaForm extends React.Component<JsonFormProps, {}> {
static defaultProps: JsonFormProps;
constructor(props) {
super(props);
}
render() {
if (this.props.form.length > 0) {
return <ThemeProvider theme={tbTheme}><ThingsboardSchemaForm {...this.props} /></ThemeProvider>;
} else {
return <div></div>;
}
}
}
ReactSchemaForm.defaultProps = {
isFullscreen: false,
schema: {},
form: ['*'],
groupInfoes: [],
option: {
formDefaults: {
startEmpty: true
}
}
};
export default ReactSchemaForm;

217
ui-ngx/src/app/shared/components/json-form/react/json-form-schema-form.tsx

@ -1,217 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import JsonFormUtils from './json-form-utils';
import ThingsboardArray from './json-form-array';
import ThingsboardJavaScript from './json-form-javascript';
import ThingsboardJson from './json-form-json';
import ThingsboardHtml from './json-form-html';
import ThingsboardCss from './json-form-css';
import ThingsboardColor from './json-form-color';
import ThingsboardRcSelect from './json-form-rc-select';
import ThingsboardNumber from './json-form-number';
import ThingsboardText from './json-form-text';
import ThingsboardSelect from './json-form-select';
import ThingsboardRadios from './json-form-radios';
import ThingsboardDate from './json-form-date';
import ThingsboardImage from './json-form-image';
import ThingsboardCheckbox from './json-form-checkbox';
import ThingsboardHelp from './json-form-help';
import ThingsboardFieldSet from './json-form-fieldset';
import ThingsboardIcon from './json-form-icon';
import {
JsonFormData,
JsonFormProps,
onChangeFn,
OnColorClickFn, onHelpClickFn,
OnIconClickFn,
onToggleFullscreenFn
} from './json-form.models';
import _ from 'lodash';
import tinycolor from 'tinycolor2';
import { GroupInfo } from '@shared/models/widget.models';
import ThingsboardMarkdown from '@shared/components/json-form/react/json-form-markdown';
import { MouseEvent, ReactNode } from 'react';
class ThingsboardSchemaForm extends React.Component<JsonFormProps, any> {
private hasConditions: boolean;
private conditionFunction: Function;
private readonly mapper: {[type: string]: any};
constructor(props: JsonFormProps) {
super(props);
this.mapper = {
number: ThingsboardNumber,
text: ThingsboardText,
password: ThingsboardText,
textarea: ThingsboardText,
select: ThingsboardSelect,
radios: ThingsboardRadios,
date: ThingsboardDate,
image: ThingsboardImage,
checkbox: ThingsboardCheckbox,
help: ThingsboardHelp,
array: ThingsboardArray,
javascript: ThingsboardJavaScript,
json: ThingsboardJson,
html: ThingsboardHtml,
css: ThingsboardCss,
markdown: ThingsboardMarkdown,
color: ThingsboardColor,
'rc-select': ThingsboardRcSelect,
fieldset: ThingsboardFieldSet,
icon: ThingsboardIcon
};
this.onChange = this.onChange.bind(this);
this.onColorClick = this.onColorClick.bind(this);
this.onIconClick = this.onIconClick.bind(this);
this.onToggleFullscreen = this.onToggleFullscreen.bind(this);
this.onHelpClick = this.onHelpClick.bind(this);
this.hasConditions = false;
}
onChange(key: (string | number)[], val: any, forceUpdate?: boolean) {
this.props.onModelChange(key, val, forceUpdate);
if (this.hasConditions) {
this.forceUpdate();
}
}
onColorClick(key: (string | number)[], val: tinycolor.ColorFormats.RGBA,
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) {
this.props.onColorClick(key, val, colorSelectedFn);
}
onIconClick(key: (string | number)[], val: string,
iconSelectedFn: (icon: string) => void) {
this.props.onIconClick(key, val, iconSelectedFn);
}
onToggleFullscreen(fullscreenFinishFn?: (el: Element) => void) {
this.props.onToggleFullscreen(fullscreenFinishFn);
}
onHelpClick(event: MouseEvent, helpId: string, helpVisibleFn: (visible: boolean) => void, helpReadyFn: (ready: boolean) => void) {
this.props.onHelpClick(event, helpId, helpVisibleFn, helpReadyFn);
}
builder(form: JsonFormData,
model: any,
index: number,
onChange: onChangeFn,
onColorClick: OnColorClickFn,
onIconClick: OnIconClickFn,
onToggleFullscreen: onToggleFullscreenFn,
onHelpClick: onHelpClickFn,
mapper: {[type: string]: any}): React.JSX.Element {
const type = form.type;
const Field = this.mapper[type];
if (!Field) {
console.log('Invalid field: \"' + form.key[0] + '\"!');
return null;
}
if (form.condition) {
this.hasConditions = true;
if (!form.conditionFunction) {
form.conditionFunction = new Function('form', 'model', 'index', `return ${form.condition};`);
}
if (form.conditionFunction(form, model, index) === false) {
return null;
}
}
return <Field model={model} form={form} key={index} onChange={onChange}
onColorClick={onColorClick}
onIconClick={onIconClick}
onToggleFullscreen={onToggleFullscreen}
onHelpClick={onHelpClick}
mapper={mapper} builder={this.builder}/>;
}
createSchema(theForm: any[]): React.JSX.Element {
const merged = JsonFormUtils.merge(this.props.schema, theForm, this.props.ignore, this.props.option);
let mapper = this.mapper;
if (this.props.mapper) {
mapper = _.merge(this.mapper, this.props.mapper);
}
const forms: ReactNode[] = merged.map(function(form: JsonFormData, index: number) {
return this.builder(form, this.props.model, index, this.onChange, this.onColorClick,
this.onIconClick, this.onToggleFullscreen, this.onHelpClick, mapper);
}.bind(this));
let formClass = 'SchemaForm';
if (this.props.isFullscreen) {
formClass += ' SchemaFormFullscreen';
}
return (
<div style={{width: '100%'}} className={formClass}>{forms}</div>
);
}
render() {
if (this.props.groupInfoes && this.props.groupInfoes.length > 0) {
const content: React.JSX.Element[] = [];
for (const info of this.props.groupInfoes) {
const forms = this.createSchema(this.props.form[info.formIndex]);
const item = <ThingsboardSchemaGroup key={content.length} forms={forms} info={info}></ThingsboardSchemaGroup>;
content.push(item);
}
return (<div>{content}</div>);
} else {
return this.createSchema(this.props.form);
}
}
}
export default ThingsboardSchemaForm;
interface ThingsboardSchemaGroupProps {
info: GroupInfo;
forms: React.JSX.Element;
}
interface ThingsboardSchemaGroupState {
showGroup: boolean;
}
class ThingsboardSchemaGroup extends React.Component<ThingsboardSchemaGroupProps, ThingsboardSchemaGroupState> {
constructor(props: ThingsboardSchemaGroupProps) {
super(props);
this.state = {
showGroup: true
};
}
toogleGroup() {
this.setState({
showGroup: !this.state.showGroup
});
}
render() {
const theCla = 'pull-right fa fa-chevron-down tb-toggle-icon' + (this.state.showGroup ? '' : ' tb-toggled');
return (<section className='mat-elevation-z1' style={{marginTop: '10px'}}>
<div className='SchemaGroupname tb-button-toggle'
onClick={this.toogleGroup.bind(this)}>{this.props.info.GroupTitle}<span className={theCla}></span></div>
<div style={{padding: '20px'}} className={this.state.showGroup ? '' : 'invisible'}>{this.props.forms}</div>
</section>);
}
}

93
ui-ngx/src/app/shared/components/json-form/react/json-form-select.tsx

@ -1,93 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import ThingsboardBaseComponent from '@shared/components/json-form/react/json-form-base-component';
import { isObject } from '@core/utils';
interface ThingsboardSelectState extends JsonFormFieldState {
currentValue: any;
}
class ThingsboardSelect extends React.Component<JsonFormFieldProps, ThingsboardSelectState> {
static getDerivedStateFromProps(props: JsonFormFieldProps) {
if (props.model && props.form.key) {
return {
currentValue: ThingsboardSelect.getModelKey(props.model, props.form.key)
|| (props.form.titleMap != null ? props.form.titleMap[0].value : '')
}
}
}
static getModelKey(model: any, key: (string | number)[]) {
if (Array.isArray(key)) {
const res = key.reduce((cur, nxt) => (cur[nxt] || {}), model);
if (res && isObject(res)) {
return undefined;
} else {
return res;
}
} else {
return model[key];
}
}
constructor(props: JsonFormFieldProps) {
super(props);
this.onSelected = this.onSelected.bind(this);
const possibleValue = ThingsboardSelect.getModelKey(this.props.model, this.props.form.key);
this.state = {
currentValue: this.props.model !== undefined && possibleValue ? possibleValue : this.props.form.titleMap != null ?
this.props.form.titleMap[0].value : ''
};
}
onSelected(event: SelectChangeEvent<any>) {
this.setState({
currentValue: event.target.value
});
this.props.onChangeValidate(event);
}
render() {
const menuItems = this.props.form.titleMap.map((item, idx) => (
<MenuItem key={idx}
value={item.value}>{item.name}</MenuItem>
));
return (
<FormControl className={this.props.form.htmlClass}
disabled={this.props.form.readonly}
fullWidth={true}>
<InputLabel variant={'standard'} htmlFor='select-field'>{this.props.form.title}</InputLabel>
<Select
variant={'standard'}
value={this.state.currentValue}
onChange={this.onSelected}>
{menuItems}
</Select>
</FormControl>
);
}
}
export default ThingsboardBaseComponent(ThingsboardSelect);

92
ui-ngx/src/app/shared/components/json-form/react/json-form-text.tsx

@ -1,92 +0,0 @@
/*
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import ThingsboardBaseComponent from './json-form-base-component';
import TextField from '@mui/material/TextField';
import { JsonFormFieldProps, JsonFormFieldState } from '@shared/components/json-form/react/json-form.models';
interface ThingsboardTextState extends JsonFormFieldState {
focused: boolean;
}
class ThingsboardText extends React.Component<JsonFormFieldProps, ThingsboardTextState> {
constructor(props: JsonFormFieldProps) {
super(props);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
this.state = {
focused: false
};
}
onBlur() {
this.setState({focused: false});
}
onFocus() {
this.setState({focused: true});
}
render() {
let fieldClass = 'tb-field';
if (this.props.form.required) {
fieldClass += ' tb-required';
}
if (this.props.form.readonly) {
fieldClass += ' tb-readonly';
}
if (this.state.focused) {
fieldClass += ' tb-focused';
}
const multiline = this.props.form.type === 'textarea';
let rows = 1;
let rowsMax = 1;
let minHeight = 48;
if (multiline) {
rows = this.props.form.rows || 2;
rowsMax = this.props.form.rowsMax;
minHeight = 19 * rows + 48;
}
return (
<div>
<TextField
variant={'standard'}
className={fieldClass}
type={this.props.form.type}
label={this.props.form.title}
multiline={multiline}
error={!this.props.valid}
helperText={this.props.valid ? this.props.form.placeholder : this.props.error}
onChange={(e) => {
this.props.onChangeValidate(e);
}}
defaultValue={this.props.value}
disabled={this.props.form.readonly}
rows={rows}
maxRows={rowsMax}
onFocus={this.onFocus}
onBlur={this.onBlur}
style={this.props.form.style || {width: '100%', minHeight: minHeight + 'px'}}/>
</div>
);
}
}
export default ThingsboardBaseComponent(ThingsboardText);

141
ui-ngx/src/app/shared/components/json-form/react/json-form.models.ts

@ -1,141 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import tinycolor from 'tinycolor2';
import { GroupInfo } from '@shared/models/widget.models';
import { MouseEvent } from 'react';
export interface SchemaValidationResult {
valid: boolean;
error?: {
message?: string;
};
}
export interface FormOption {
formDefaults?: {
startEmpty?: boolean;
readonly?: boolean;
};
supressPropertyTitles?: boolean;
}
export interface DefaultsFormOptions {
global?: FormOption;
required?: boolean;
path?: string[];
lookup?: {[key: string]: any};
ignore?: {[key: string]: boolean};
}
export type onChangeFn = (key: (string | number)[], val: any, forceUpdate?: boolean) => void;
export type OnColorClickFn = (key: (string | number)[], val: tinycolor.ColorFormats.RGBA,
colorSelectedFn: (color: tinycolor.ColorFormats.RGBA) => void) => void;
export type OnIconClickFn = (key: (string | number)[], val: string,
iconSelectedFn: (icon: string) => void) => void;
export type onToggleFullscreenFn = (fullscreenFinishFn?: (el: Element) => void) => void;
export type onHelpClickFn = (event: MouseEvent, helpId: string, helpVisibleFn: (visible: boolean) => void,
helpReadyFn: (ready: boolean) => void) => void;
export interface JsonFormProps {
model?: any;
schema?: any;
form?: any;
groupInfoes?: GroupInfo[];
isFullscreen: boolean;
ignore?: {[key: string]: boolean};
option: FormOption;
onModelChange?: onChangeFn;
onColorClick?: OnColorClickFn;
onIconClick?: OnIconClickFn;
onToggleFullscreen?: onToggleFullscreenFn;
onHelpClick?: onHelpClickFn;
mapper?: {[type: string]: any};
}
export interface KeyLabelItem {
key: string;
label: string;
value?: string;
}
export interface JsonSchemaData {
type: string;
default: any;
items?: JsonSchemaData;
properties?: any;
}
export interface JsonFormData {
type: string;
key: (string | number)[];
title: string;
readonly: boolean;
required: boolean;
default?: any;
condition?: string;
conditionFunction?: Function;
style?: any;
rows?: number;
rowsMax?: number;
placeholder?: string;
schema: JsonSchemaData;
titleMap: {
value: any;
name: string;
}[];
items?: Array<KeyLabelItem> | Array<JsonFormData>;
tabs?: Array<JsonFormData>;
tags?: any;
helpId?: string;
startEmpty?: boolean;
[key: string]: any;
}
export type ComponentBuilderFn = (form: JsonFormData,
model: any,
index: number,
onChange: onChangeFn,
onColorClick: OnColorClickFn,
onIconClick: OnIconClickFn,
onToggleFullscreen: onToggleFullscreenFn,
onHelpClick: onHelpClickFn,
mapper: {[type: string]: any}) => JSX.Element;
export interface JsonFormFieldProps {
value: any;
model: any;
form: JsonFormData;
builder: ComponentBuilderFn;
mapper?: {[type: string]: any};
onChange?: onChangeFn;
onColorClick?: OnColorClickFn;
onIconClick?: OnIconClickFn;
onChangeValidate?: (e: any, forceUpdate?: boolean) => void;
onToggleFullscreen?: onToggleFullscreenFn;
onHelpClick?: onHelpClickFn;
valid?: boolean;
error?: string;
options?: {
setSchemaDefaults?: boolean;
};
}
export interface JsonFormFieldState {
value?: any;
valid?: boolean;
error?: string;
}

361
ui-ngx/src/app/shared/components/json-form/react/json-form.scss

@ -1,361 +0,0 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$swift-ease-out-duration: .4s !default;
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
$input-label-float-offset: 6px !default;
$input-label-float-scale: .75 !default;
$previewSize: 100px !default;
.tb-json-form {
&.tb-fullscreen {
background: #fff;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
> div.fullscreen-form-field {
position: relative;
width: 100%;
height: 100%;
}
}
.json-form-error {
position: relative;
bottom: -5px;
font-size: 12px;
line-height: 12px;
color: rgb(244, 67, 54);
transition: all 450ms cubic-bezier(.23, 1, .32, 1) 0ms;
}
.tb-container {
position: relative;
box-sizing: border-box;
padding: 10px 0;
margin-top: 32px;
}
.tb-field {
padding-bottom: 18px;
.MuiInputBase-multiline {
flex: 1;
flex-direction: column;
.MuiInputBase-inputMultiline {
flex: 1;
}
}
&.tb-required {
label::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
}
&.tb-focused:not(.tb-readonly) {
label::after {
color: rgb(221, 44, 0);
}
}
}
.tb-date-field {
&.tb-required {
div > div:first-child::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
}
&.tb-focused:not(.tb-readonly) {
div > div:first-child::after {
color: rgb(221, 44, 0);
}
}
}
label.tb-label {
position: absolute;
right: auto;
bottom: 100%;
left: 0;
color: rgba(0, 0, 0, .54);
transition: transform $swift-ease-out-timing-function $swift-ease-out-duration, width $swift-ease-out-timing-function $swift-ease-out-duration;
transform: translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale);
transform-origin: left top;
-webkit-font-smoothing: antialiased;
&.tb-focused {
color: rgb(96, 125, 139);
}
&.tb-required::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
&.tb-focused:not(.tb-readonly)::after {
color: rgb(221, 44, 0);
}
}
.tb-head-label {
color: rgba(0, 0, 0, .54);
padding-bottom: 15px;
}
.SchemaGroupname {
padding: 10px 20px;
background-color: #f1f1f1;
}
.invisible {
display: none;
}
.tb-button-toggle .tb-toggle-icon {
display: inline-block;
width: 15px;
margin: auto 0 auto auto;
background-size: 100% auto;
transition: transform .3s, ease-in-out;
}
.tb-button-toggle .tb-toggle-icon.tb-toggled {
transform: rotateZ(180deg);
}
.fullscreen-form-field {
.json-form-ace-editor {
height: calc(100% - 60px);
}
}
.json-form-ace-editor {
position: relative;
height: 100%;
border: 1px solid #c0c0c0;
.title-panel {
position: absolute;
top: 10px;
right: 20px;
z-index: 5;
font-size: .8rem;
font-weight: 500;
label {
padding: 4px;
color: #00acc1;
background: rgba(220, 220, 220, .35);
border-radius: 5px;
}
button.tidy-button {
background: rgba(220, 220, 220, .35) !important;
span {
padding: 0 !important;
font-size: 12px !important;
}
}
button.help-button {
background: rgba(220, 220, 220, .35);
padding: 4px;
}
div.help-button-loading {
pointer-events: none;
background: #f3f3f3;
border-radius: 50%;
display: flex;
place-content: center;
align-items: center;
}
}
}
.tb-image-select-container {
position: relative;
width: 100%;
height: $previewSize;
}
.tb-image-preview {
width: auto;
max-width: $previewSize;
height: auto;
max-height: $previewSize;
}
.tb-image-preview-container {
position: relative;
float: left;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
vertical-align: top;
border: solid 1px;
div {
width: 100%;
font-size: 18px;
text-align: center;
}
div, .tb-image-preview {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.tb-dropzone {
outline: none;
position: relative;
height: $previewSize;
padding: 0 8px;
overflow: hidden;
vertical-align: top;
border: dashed 2px;
div {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
font-size: 24px;
text-align: center;
transform: translate(-50%, -50%);
}
}
.tb-image-clear-container {
position: relative;
float: right;
width: 48px;
height: $previewSize;
}
.tb-image-clear-btn {
position: absolute !important;
top: 50%;
transform: translate(0%, -50%) !important;
}
.MuiButton-root {
text-transform: none;
}
}
.rc-select {
box-sizing: border-box;
display: inline-block;
position: relative;
vertical-align: middle;
color: #666;
line-height: 28px;
font-size: inherit !important;
.rc-select-selector {
outline: none;
user-select: none;
box-sizing: border-box;
display: block;
background-color: #fff;
border-radius: 6px;
}
&.rc-select-single {
&:not(.rc-select-customize-input) {
.rc-select-selector {
height: 28px;
line-height: 28px;
position: relative;
border: 1px solid #d9d9d9;
&:hover {
border-color: #23c0fa;
box-shadow: 0 0 2px rgba(45, 183, 245, 0.8);
}
.rc-select-selection-search {
.rc-select-selection-search-input {
cursor: pointer;
background: transparent;
margin-left: 10px;
}
}
.rc-select-selection-item, .rc-select-selection-placeholder {
top: 0;
left: 10px;
}
}
&.rc-select-focused {
.rc-select-selector {
border-color: #23c0fa !important;
box-shadow: 0 0 2px rgba(45, 183, 245, 0.8) !important;
}
}
}
}
}
.rc-select-dropdown {
&.tb-rc-select-dropdown {
z-index: 100001;
background-color: white;
border: 1px solid #d9d9d9;
box-shadow: 0 0 4px #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
outline: none;
.rc-select-item {
&.rc-select-item-option {
margin: 0;
position: relative;
display: block;
padding: 7px 10px;
font-weight: normal;
color: #666;
white-space: nowrap;
&.rc-select-item-option-selected {
color: #666;
background-color: #ddd;
}
&.rc-select-item-option-active {
background-color: #5897fb;
color: white;
cursor: pointer;
}
}
}
}
}

46
ui-ngx/src/app/shared/components/json-form/react/styles/thingsboardTheme.ts

@ -1,46 +0,0 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { indigo, deepOrange } from '@mui/material/colors';
import { ThemeOptions } from '@mui/material/styles';
import { PaletteOptions } from '@mui/material/styles/createPalette';
import { mergeDeep } from '@core/utils';
const PRIMARY_COLOR = '#305680';
const SECONDARY_COLOR = '#527dad';
const HUE3_COLOR = '#a7c1de';
const tbIndigo = mergeDeep<any>({}, indigo, {
500: PRIMARY_COLOR,
600: SECONDARY_COLOR,
700: PRIMARY_COLOR,
A100: HUE3_COLOR
});
const thingsboardPalette: PaletteOptions = {
primary: tbIndigo,
secondary: deepOrange,
background: {
default: '#eee'
}
};
export default {
typography: {
fontFamily: 'Roboto, \'Helvetica Neue\', sans-serif'
},
palette: thingsboardPalette,
} as ThemeOptions;

203
ui-ngx/src/app/shared/components/json-form/react/json-form-utils.ts → ui-ngx/src/app/shared/legacy/json-form-utils.ts

@ -14,63 +14,12 @@
/// limitations under the License.
///
import * as tv from 'tv4';
import ObjectPath from 'objectpath';
import _ from 'lodash';
import {
DefaultsFormOptions,
FormOption,
JsonFormData,
JsonSchemaData,
SchemaValidationResult
FormOption
} from './json-form.models';
import { isDefined, isEqual, isString, isUndefined } from '@core/utils';
function validateBySchema(schema: any, value: any): SchemaValidationResult {
return tv.validateResult(value, schema);
}
function validate(form: any, value: any): SchemaValidationResult {
if (!form) {
return {valid: true};
}
const schema = form.schema;
if (!schema) {
return {valid: true};
}
if (value === '') {
value = undefined;
}
// Numbers fields will give a null value, which also means empty field
if (form.type === 'number' && value === null) {
value = undefined;
}
if (form.type === 'number' && isNaN(parseFloat(value))) {
value = undefined;
}
const wrap: any = {type: 'object', properties: {}};
const propName = form.key[form.key.length - 1];
wrap.properties[propName] = schema;
if (form.required) {
wrap.required = [propName];
}
const valueWrap = {};
if (typeof value !== 'undefined') {
valueWrap[propName] = value;
}
const tv4Result: SchemaValidationResult = tv.validateResult(valueWrap, wrap);
if (tv4Result != null && !tv4Result.valid && form.validationMessage != null && typeof value !== 'undefined') {
tv4Result.error.message = form.validationMessage;
}
return tv4Result;
}
function stripNullType(type: any): string {
if (Array.isArray(type) && type.length === 2) {
@ -438,155 +387,7 @@ function merge(schema: any, form: any[], ignore: { [key: string]: boolean }, opt
}));
}
function selectOrSet(projection: string | (string | number)[], obj: any, valueToSet?: any): any {
const numRe = /^\d+$/;
if (!obj) {
obj = this;
}
const parts = typeof projection === 'string' ? ObjectPath.parse(projection) : projection;
if (typeof valueToSet !== 'undefined' && parts.length === 1) {
obj[parts[0]] = valueToSet;
return obj;
}
if (typeof valueToSet !== 'undefined' && typeof obj[parts[0]] === 'undefined') {
obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {};
}
let value = obj[parts[0]];
for (let i = 1; i < parts.length; i++) {
if (parts[i] === '') {
return undefined;
}
if (typeof valueToSet !== 'undefined') {
if (i === parts.length - 1) {
value[parts[i]] = valueToSet;
return valueToSet;
} else {
let tmp = value[parts[i]];
if (typeof tmp === 'undefined' || tmp === null) {
tmp = numRe.test(parts[i + 1]) ? [] : {};
value[parts[i]] = tmp;
}
value = tmp;
}
} else if (value) {
value = value[parts[i]];
}
}
return value;
}
function updateValue(projection: string | (string | number)[], obj: any, valueToSet: any): boolean {
const numRe = /^\d+$/;
if (!obj) {
obj = this;
}
if (!obj) {
return false;
}
const parts: string[] = isString(projection) ? ObjectPath.parse(projection) : projection;
if (parts.length === 1) {
return setValue(obj, parts[0], valueToSet);
}
if (isUndefined(obj[parts[0]])) {
obj[parts[0]] = parts.length > 2 && numRe.test(parts[1]) ? [] : {};
}
let value = obj[parts[0]];
for (let i = 1; i < parts.length; i++) {
if (parts[i] === '') {
return false;
}
if (i === parts.length - 1) {
return setValue(value, parts[i], valueToSet);
} else {
let tmp = value[parts[i]];
if (isUndefined(tmp) || tmp === null) {
tmp = numRe.test(parts[i + 1]) ? [] : {};
value[parts[i]] = tmp;
}
value = tmp;
}
}
return value;
}
function setValue(obj: any, key: string, val: any): boolean {
let changed = false;
if (obj) {
if (isUndefined(val)) {
if (isDefined(obj[key])) {
delete obj[key];
changed = true;
}
} else {
changed = !isEqual(obj[key], val);
obj[key] = val;
}
}
return changed;
}
function traverseSchema(schema: JsonSchemaData, fn: (prop: any, path: string[]) => any, path?: string[], ignoreArrays?: boolean) {
ignoreArrays = typeof ignoreArrays !== 'undefined' ? ignoreArrays : true;
path = path || [];
const traverse = ($schema: JsonSchemaData, $fn: (prop: any, path: string[]) => any, $path: string[]) => {
$fn($schema, $path);
if ($schema.properties) {
for (const k of Object.keys($schema.properties)) {
if ($schema.properties.hasOwnProperty(k)) {
const currentPath = $path.slice();
currentPath.push(k);
traverse($schema.properties[k], $fn, currentPath);
}
}
}
if (!ignoreArrays && $schema.items) {
const arrPath = $path.slice();
arrPath.push('');
traverse($schema.items, $fn, arrPath);
}
};
traverse(schema, fn, path || []);
}
function traverseForm(form: JsonFormData, fn: (form: JsonFormData) => any) {
fn(form);
if (form.items) {
form.items.forEach((f) => {
traverseForm(f, fn);
});
}
if (form.tabs) {
form.tabs.forEach((tab) => {
tab.items.forEach((f) => {
traverseForm(f, fn);
});
});
}
}
const utils = {
validateBySchema,
validate,
merge,
updateValue,
selectOrSet,
traverseSchema,
traverseForm
merge
};
export default utils;

88
ui-ngx/src/app/shared/legacy/json-form.models.ts

@ -0,0 +1,88 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
export interface FormOption {
formDefaults?: {
startEmpty?: boolean;
readonly?: boolean;
};
supressPropertyTitles?: boolean;
}
export interface DefaultsFormOptions {
global?: FormOption;
required?: boolean;
path?: string[];
lookup?: {[key: string]: any};
ignore?: {[key: string]: boolean};
}
export interface KeyLabelItem {
key: string;
label: string;
value?: string;
}
export interface JsonSchemaData {
type: string;
default: any;
items?: JsonSchemaData;
properties?: any;
}
export interface JsonFormData {
type: string;
key: (string | number)[];
title: string;
readonly: boolean;
required: boolean;
default?: any;
condition?: string;
conditionFunction?: Function;
style?: any;
rows?: number;
rowsMax?: number;
placeholder?: string;
schema: JsonSchemaData;
titleMap: {
value: any;
name: string;
}[];
items?: Array<KeyLabelItem> | Array<JsonFormData>;
tabs?: Array<JsonFormData>;
tags?: any;
helpId?: string;
startEmpty?: boolean;
[key: string]: any;
}
export interface GroupInfo {
formIndex: number;
GroupTitle: string;
}
export interface JsonSchema {
type: string;
title?: string;
properties: {[key: string]: any};
required?: string[];
}
export interface JsonSettingsSchema {
schema?: JsonSchema;
form?: any[];
groupInfoes?: GroupInfo[];
}

48
ui-ngx/src/app/shared/models/dynamic-form.models.ts

@ -17,9 +17,8 @@
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbEditorCompletion, TbEditorCompletions } from '@shared/models/ace/completion.models';
import { deepClone, isDefinedAndNotNull, isString } from '@core/utils';
import { JsonSchema, JsonSettingsSchema } from '@shared/models/widget.models';
import JsonFormUtils from '@shared/components/json-form/react/json-form-utils';
import { JsonFormData, KeyLabelItem } from '@shared/components/json-form/react/json-form.models';
import { JsonSchema, JsonSettingsSchema, JsonFormData, KeyLabelItem } from '@shared/legacy/json-form.models';
import JsonFormUtils from '@shared/legacy/json-form-utils';
import { constantColor, Font } from '@shared/models/widget-settings.models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@ -573,29 +572,34 @@ const formPropertyCompletionType = (property: FormProperty): string => {
export const jsonFormSchemaToFormProperties = (rawSchema: string | any) : FormProperty[] => {
const properties: FormProperty[] = [];
let settingsSchema: JsonSettingsSchema;
if (!rawSchema || rawSchema === '') {
settingsSchema = {};
} else {
settingsSchema = isString(rawSchema) ? JSON.parse(rawSchema) : rawSchema;
}
if (settingsSchema.schema) {
const schema = settingsSchema.schema;
const form = settingsSchema.form || ['*'];
const groupInfoes = settingsSchema.groupInfoes || [];
if (form.length > 0) {
if (groupInfoes.length) {
for (const info of groupInfoes) {
const theForm: any[] = form[info.formIndex];
properties.push(...schemaFormToProperties(schema, theForm, info.GroupTitle));
try {
const properties: FormProperty[] = [];
let settingsSchema: JsonSettingsSchema;
if (!rawSchema || rawSchema === '') {
settingsSchema = {};
} else {
settingsSchema = isString(rawSchema) ? JSON.parse(rawSchema) : rawSchema;
}
if (settingsSchema.schema) {
const schema = settingsSchema.schema;
const form = settingsSchema.form || ['*'];
const groupInfoes = settingsSchema.groupInfoes || [];
if (form.length > 0) {
if (groupInfoes.length) {
for (const info of groupInfoes) {
const theForm: any[] = form[info.formIndex];
properties.push(...schemaFormToProperties(schema, theForm, info.GroupTitle));
}
} else {
properties.push(...schemaFormToProperties(schema, form));
}
} else {
properties.push(...schemaFormToProperties(schema, form));
}
}
return properties;
} catch (e) {
console.warn('Failed to convert old JSON form schema to form properties:', e);
return [];
}
return properties;
}
const schemaFormToProperties = (schema: JsonSchema, theForm: any[], groupTitle?: string): FormProperty[] => {

54
ui-ngx/src/app/shared/models/widget.models.ts

@ -45,7 +45,7 @@ import { HasTenantId, HasVersion } from '@shared/models/entity.models';
import { DataKeysCallbacks, DataKeySettingsFunction } from '@home/components/widget/config/data-keys.component.models';
import { WidgetConfigCallbacks } from '@home/components/widget/config/widget-config.component.models';
import { TbFunction } from '@shared/models/js-function.models';
import { FormProperty } from '@shared/models/dynamic-form.models';
import { FormProperty, jsonFormSchemaToFormProperties } from '@shared/models/dynamic-form.models';
export enum widgetType {
timeseries = 'timeseries',
@ -155,9 +155,8 @@ export interface WidgetTypeDescriptor {
templateCss: string;
controllerScript: TbFunction;
settingsForm?: FormProperty[];
settingsSchema?: string | any;
dataKeySettingsSchema?: string | any;
latestDataKeySettingsSchema?: string | any;
dataKeySettingsForm?: FormProperty[];
latestDataKeySettingsForm?: FormProperty[];
settingsDirective?: string;
dataKeySettingsDirective?: string;
latestDataKeySettingsDirective?: string;
@ -196,9 +195,8 @@ export interface WidgetTypeParameters {
export interface WidgetControllerDescriptor {
widgetTypeFunction?: any;
settingsForm?: FormProperty[];
settingsSchema?: string | any;
dataKeySettingsSchema?: string | any;
latestDataKeySettingsSchema?: string | any;
dataKeySettingsForm?: FormProperty[];
latestDataKeySettingsForm?: FormProperty[];
typeParameters?: WidgetTypeParameters;
actionSources?: {[actionSourceId: string]: WidgetActionSource};
}
@ -238,6 +236,30 @@ export const isValidWidgetFullFqn = (fullFqn: string): boolean => {
return false;
};
export const migrateWidgetTypeToDynamicForms = <T extends WidgetType>(widgetType: T): T => {
const descriptor = widgetType.descriptor;
if ((descriptor as any).settingsSchema) {
if (!descriptor.settingsForm?.length) {
descriptor.settingsForm = jsonFormSchemaToFormProperties((descriptor as any).settingsSchema);
}
delete (descriptor as any).settingsSchema;
}
if ((descriptor as any).dataKeySettingsSchema) {
if (!descriptor.dataKeySettingsForm?.length) {
descriptor.dataKeySettingsForm = jsonFormSchemaToFormProperties((descriptor as any).dataKeySettingsSchema);
}
delete (descriptor as any).dataKeySettingsSchema;
}
if ((descriptor as any).latestDataKeySettingsSchema) {
if (!descriptor.latestDataKeySettingsForm?.length) {
descriptor.latestDataKeySettingsForm = jsonFormSchemaToFormProperties((descriptor as any).latestDataKeySettingsSchema);
}
delete (descriptor as any).latestDataKeySettingsSchema;
}
return widgetType;
}
export interface WidgetType extends BaseWidgetType {
descriptor: WidgetTypeDescriptor;
}
@ -815,24 +837,6 @@ export interface WidgetInfo extends BaseWidgetInfo {
deprecated?: boolean;
}
export interface GroupInfo {
formIndex: number;
GroupTitle: string;
}
export interface JsonSchema {
type: string;
title?: string;
properties: {[key: string]: any};
required?: string[];
}
export interface JsonSettingsSchema {
schema?: JsonSchema;
form?: any[];
groupInfoes?: GroupInfo[];
}
export interface DynamicFormData {
settingsForm?: FormProperty[];
model?: any;

3
ui-ngx/src/app/shared/shared.module.ts

@ -121,7 +121,6 @@ import { TbJsonPipe } from '@shared/pipe/tbJson.pipe';
import { ColorPickerDialogComponent } from '@shared/components/dialog/color-picker-dialog.component';
import { ColorInputComponent } from '@shared/components/color-input.component';
import { JsFuncComponent } from '@shared/components/js-func.component';
import { JsonFormComponent } from '@shared/components/json-form/json-form.component';
import { ConfirmDialogComponent } from '@shared/components/dialog/confirm-dialog.component';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { ErrorAlertDialogComponent } from '@shared/components/dialog/error-alert-dialog.component';
@ -362,7 +361,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ColorInputComponent,
MaterialIconSelectComponent,
NodeScriptTestDialogComponent,
JsonFormComponent,
ImageInputComponent,
MultipleImageInputComponent,
FileInputComponent,
@ -625,7 +623,6 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
ColorInputComponent,
MaterialIconSelectComponent,
NodeScriptTestDialogComponent,
JsonFormComponent,
ImageInputComponent,
MultipleImageInputComponent,
FileInputComponent,

5
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -5481,9 +5481,8 @@
"tidy": "Tidy",
"css": "CSS",
"settings-form": "Settings form",
"settings-schema": "Settings schema",
"datakey-settings-schema": "Data key settings schema",
"latest-datakey-settings-schema": "Latest data key settings schema",
"data-key-settings-form": "Data key settings form",
"latest-data-key-settings-form": "Latest data key settings form",
"widget-settings": "Widget settings",
"description": "Description",
"tags": "Tags",

4
ui-ngx/src/styles.scss

@ -58,6 +58,10 @@ tb-root {
* TYPE DEFAULTS
***************/
* {
box-sizing: border-box;
}
body,
button,
html,

2
ui-ngx/src/tsconfig.app.json

@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": ["node", "jquery", "flot", "tooltipster", "tinycolor2", "js-beautify",
"react", "react-dom", "raphael", "canvas-gauges", "systemjs"]
"raphael", "canvas-gauges", "systemjs"]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,

1
ui-ngx/tsconfig.json

@ -14,7 +14,6 @@
"target": "ES2022",
"module": "es2020",
"emitDecoratorMetadata": true,
"jsx": "react",
"resolveJsonModule": true,
"typeRoots": [
"node_modules/@types",

730
ui-ngx/yarn.lock

@ -499,7 +499,7 @@
"@babel/traverse" "^7.25.9"
"@babel/types" "^7.25.9"
"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.24.7", "@babel/helper-module-imports@^7.25.9":
"@babel/helper-module-imports@^7.24.7", "@babel/helper-module-imports@^7.25.9":
version "7.25.9"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
@ -1296,7 +1296,7 @@
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.10.1", "@babel/runtime@^7.11.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.25.0", "@babel/runtime@^7.25.6", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.8.4":
version "7.26.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1"
integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==
@ -1382,113 +1382,6 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.6.1.tgz#593da7a17a31a72a874e313677183334a49b01c9"
integrity sha512-boghen8F0Q8D+0/Q1/1r6DUEieUJ8w2a1gIknExMSHBsJFOr2+0KUfHiVYBvucPwl3+RU5PFBK833FjFCh3BhA==
"@emotion/babel-plugin@^11.12.0":
version "11.12.0"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz#7b43debb250c313101b3f885eba634f1d723fcc2"
integrity sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==
dependencies:
"@babel/helper-module-imports" "^7.16.7"
"@babel/runtime" "^7.18.3"
"@emotion/hash" "^0.9.2"
"@emotion/memoize" "^0.9.0"
"@emotion/serialize" "^1.2.0"
babel-plugin-macros "^3.1.0"
convert-source-map "^1.5.0"
escape-string-regexp "^4.0.0"
find-root "^1.1.0"
source-map "^0.5.7"
stylis "4.2.0"
"@emotion/cache@^11.13.0", "@emotion/cache@^11.13.1":
version "11.13.1"
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.13.1.tgz#fecfc54d51810beebf05bf2a161271a1a91895d7"
integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
dependencies:
"@emotion/memoize" "^0.9.0"
"@emotion/sheet" "^1.4.0"
"@emotion/utils" "^1.4.0"
"@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0"
"@emotion/hash@^0.9.2":
version "0.9.2"
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b"
integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
"@emotion/is-prop-valid@^1.3.0":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz#8d5cf1132f836d7adbe42cf0b49df7816fc88240"
integrity sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==
dependencies:
"@emotion/memoize" "^0.9.0"
"@emotion/memoize@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102"
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
"@emotion/react@11.13.3":
version "11.13.3"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.13.3.tgz#a69d0de2a23f5b48e0acf210416638010e4bd2e4"
integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.12.0"
"@emotion/cache" "^11.13.0"
"@emotion/serialize" "^1.3.1"
"@emotion/use-insertion-effect-with-fallbacks" "^1.1.0"
"@emotion/utils" "^1.4.0"
"@emotion/weak-memoize" "^0.4.0"
hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0", "@emotion/serialize@^1.3.1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.2.tgz#e1c1a2e90708d5d85d81ccaee2dfeb3cc0cccf7a"
integrity sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==
dependencies:
"@emotion/hash" "^0.9.2"
"@emotion/memoize" "^0.9.0"
"@emotion/unitless" "^0.10.0"
"@emotion/utils" "^1.4.1"
csstype "^3.0.2"
"@emotion/sheet@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
"@emotion/styled@11.13.0":
version "11.13.0"
resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.13.0.tgz#633fd700db701472c7a5dbef54d6f9834e9fb190"
integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==
dependencies:
"@babel/runtime" "^7.18.3"
"@emotion/babel-plugin" "^11.12.0"
"@emotion/is-prop-valid" "^1.3.0"
"@emotion/serialize" "^1.3.0"
"@emotion/use-insertion-effect-with-fallbacks" "^1.1.0"
"@emotion/utils" "^1.4.0"
"@emotion/unitless@^0.10.0":
version "0.10.0"
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745"
integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==
"@emotion/use-insertion-effect-with-fallbacks@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz#1a818a0b2c481efba0cf34e5ab1e0cb2dcb9dfaf"
integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==
"@emotion/utils@^1.4.0", "@emotion/utils@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.1.tgz#b3adbb43de12ee2149541c4f1337d2eb7774f0ad"
integrity sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==
"@emotion/weak-memoize@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
"@es-joy/jsdoccomment@~0.49.0":
version "0.49.0"
resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.49.0.tgz#e5ec1eda837c802eca67d3b29e577197f14ba1db"
@ -1676,33 +1569,6 @@
dependencies:
levn "^0.4.1"
"@floating-ui/core@^1.6.0":
version "1.6.8"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.8.tgz#aa43561be075815879305965020f492cdb43da12"
integrity sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==
dependencies:
"@floating-ui/utils" "^0.2.8"
"@floating-ui/dom@^1.0.0":
version "1.6.11"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.11.tgz#8631857838d34ee5712339eb7cbdfb8ad34da723"
integrity sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==
dependencies:
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.8"
"@floating-ui/react-dom@^2.1.1":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==
dependencies:
"@floating-ui/dom" "^1.0.0"
"@floating-ui/utils@^0.2.8":
version "0.2.8"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
"@flowjs/flow.js@^2.14.1":
version "2.14.1"
resolved "https://registry.yarnpkg.com/@flowjs/flow.js/-/flow.js-2.14.1.tgz#267d9f9d0958f32267ea5815c2a7cc09b9219304"
@ -2150,181 +2016,6 @@
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz#0aa5502d547b57abfc4ac492de68e2006e417242"
integrity sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==
"@mui/base@5.0.0-beta.58":
version "5.0.0-beta.58"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.58.tgz#66ae4e1aaef8cfd9ae81bd55a70ce76b02eb5d3e"
integrity sha512-P0E7ZrxOuyYqBvVv9w8k7wm+Xzx/KRu+BGgFcR2htTsGCpJNQJCSUXNUZ50MUmSU9hzqhwbQWNXhV1MBTl6F7A==
dependencies:
"@babel/runtime" "^7.25.0"
"@floating-ui/react-dom" "^2.1.1"
"@mui/types" "^7.2.15"
"@mui/utils" "6.0.0-rc.0"
"@popperjs/core" "^2.11.8"
clsx "^2.1.1"
prop-types "^15.8.1"
"@mui/core-downloads-tracker@^6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.2.tgz#a15eb14d433100f734e56929f842c2ccc7cab691"
integrity sha512-1oE4U38/TtzLWRYWEm/m70dUbpcvBx0QvDVg6NtpOmSNQC1Mbx0X/rNvYDdZnn8DIsAiVQ+SZ3am6doSswUQ4g==
"@mui/icons-material@6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-6.1.2.tgz#3e4537c312687afbdd2fd289d5412731d5da3d11"
integrity sha512-7NNcjW5JoT9jHagrVbARA1o41vQY2xezDamtke+mEKKZmsJyejfRBOacSrPDfjZQ//lyhIjNKyzAwisxYJR47w==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/lab@6.0.0-beta.10":
version "6.0.0-beta.10"
resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-6.0.0-beta.10.tgz#cf6dce21e8491aa00facc0d6b1cd357bfb2ed58e"
integrity sha512-eqCBz5SZS8Un9To3UcjH01AxkOOgvme/g0ZstFC8Nz1Kg5/EJMA0ByhKS5AvUMzUKrv0FXMdbuPqbBvF3bVrXg==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/base" "5.0.0-beta.58"
"@mui/system" "^6.1.1"
"@mui/types" "^7.2.17"
"@mui/utils" "^6.1.1"
clsx "^2.1.1"
prop-types "^15.8.1"
"@mui/material@6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-6.1.2.tgz#9f47bfa6adcf3b8245799cbf4c027e3cb949bcc6"
integrity sha512-5TtHeAVX9D5d2LYfB1GAUn29BcVETVsrQ76Dwb2SpAfQGW3JVy4deJCAd0RrIkI3eEUrsl0E4xuBdreszxdTTg==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/core-downloads-tracker" "^6.1.2"
"@mui/system" "^6.1.2"
"@mui/types" "^7.2.17"
"@mui/utils" "^6.1.2"
"@popperjs/core" "^2.11.8"
"@types/react-transition-group" "^4.4.11"
clsx "^2.1.1"
csstype "^3.1.3"
prop-types "^15.8.1"
react-is "^18.3.1"
react-transition-group "^4.4.5"
"@mui/private-theming@^6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-6.1.2.tgz#1e093c7194dd9f8a511179e0e5c5b10798a4bfae"
integrity sha512-S8WcjZdNdi++8UhrrY8Lton5h/suRiQexvdTfdcPAlbajlvgM+kx+uJstuVIEyTb3gMkxzIZep87knZ0tqcR0g==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/utils" "^6.1.2"
prop-types "^15.8.1"
"@mui/styled-engine@^6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-6.1.2.tgz#bef156ac2e47a67d49982ddb5fa4211974740a26"
integrity sha512-uKOfWkR23X39xj7th2nyTcCHqInTAXtUnqD3T5qRVdJcOPvu1rlgTleTwJC/FJvWZJBU6ieuTWDhbcx5SNViHQ==
dependencies:
"@babel/runtime" "^7.25.6"
"@emotion/cache" "^11.13.1"
"@emotion/sheet" "^1.4.0"
csstype "^3.1.3"
prop-types "^15.8.1"
"@mui/styles@6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/styles/-/styles-6.1.2.tgz#24bc654cdb0aae369348453ee8d25e3a9c1ace56"
integrity sha512-fsQkTCyyBnjsmy7CM0LG95PJZAhTsmoC/iNk4ihVYmdubMQEeGXzeAWL8E6QBChCnANmjZwm2h5ENyLnCUUuzg==
dependencies:
"@babel/runtime" "^7.25.6"
"@emotion/hash" "^0.9.2"
"@mui/private-theming" "^6.1.2"
"@mui/types" "^7.2.17"
"@mui/utils" "^6.1.2"
clsx "^2.1.1"
csstype "^3.1.3"
hoist-non-react-statics "^3.3.2"
jss "^10.10.0"
jss-plugin-camel-case "^10.10.0"
jss-plugin-default-unit "^10.10.0"
jss-plugin-global "^10.10.0"
jss-plugin-nested "^10.10.0"
jss-plugin-props-sort "^10.10.0"
jss-plugin-rule-value-function "^10.10.0"
jss-plugin-vendor-prefixer "^10.10.0"
prop-types "^15.8.1"
"@mui/system@6.1.2", "@mui/system@^6.1.1", "@mui/system@^6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-6.1.2.tgz#28840b04c6fc70780620759d67de2c20bdc7d1c7"
integrity sha512-mzW7F1ZMIYS1aLON48Nrk9c65OrVEVQ+R4lUcTWs1lCSul0VGK23eo4dmY0NX5PS7Oe4xz3P5B9tQZZ7SYgxcg==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/private-theming" "^6.1.2"
"@mui/styled-engine" "^6.1.2"
"@mui/types" "^7.2.17"
"@mui/utils" "^6.1.2"
clsx "^2.1.1"
csstype "^3.1.3"
prop-types "^15.8.1"
"@mui/types@^7.2.15", "@mui/types@^7.2.17":
version "7.2.17"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.17.tgz#329826062d4079de5ea2b97007575cebbba1fdbc"
integrity sha512-oyumoJgB6jDV8JFzRqjBo2daUuHpzDjoO/e3IrRhhHo/FxJlaVhET6mcNrKHUq2E+R+q3ql0qAtvQ4rfWHhAeQ==
"@mui/utils@6.0.0-rc.0":
version "6.0.0-rc.0"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-6.0.0-rc.0.tgz#208c12c919b5cd1731f9d14784c05c35294a893e"
integrity sha512-tBp0ILEXDL0bbDDT8PnZOjCqSm5Dfk2N0Z45uzRw+wVl6fVvloC9zw8avl+OdX1Bg3ubs/ttKn8nRNv17bpM5A==
dependencies:
"@babel/runtime" "^7.25.0"
"@mui/types" "^7.2.15"
"@types/prop-types" "^15.7.12"
clsx "^2.1.1"
prop-types "^15.8.1"
react-is "^18.3.1"
"@mui/utils@^5.16.6":
version "5.16.6"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.6.tgz#905875bbc58d3dcc24531c3314a6807aba22a711"
integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/types" "^7.2.15"
"@types/prop-types" "^15.7.12"
clsx "^2.1.1"
prop-types "^15.8.1"
react-is "^18.3.1"
"@mui/utils@^6.1.1", "@mui/utils@^6.1.2":
version "6.1.2"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-6.1.2.tgz#3717cd9373324a92e48c34f74385350104be652c"
integrity sha512-6+B1YZ8cCBWD1fc3RjqpclF9UA0MLUiuXhyCO+XowD/Z2ku5IlxeEhHHlgglyBWFGMu4kib4YU3CDsG5/zVjJQ==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/types" "^7.2.17"
"@types/prop-types" "^15.7.13"
clsx "^2.1.1"
prop-types "^15.8.1"
react-is "^18.3.1"
"@mui/x-date-pickers@7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-7.18.0.tgz#264158195aeeaf32a00519718f6c67c165b06711"
integrity sha512-12tXIoMj9vpS8fS/bS3kWPCoVrH38vNGCxgplI0vOnUrN9rJuYJz3agLPJe1S0xciTw+9W8ZSe3soaW+owoz1Q==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/utils" "^5.16.6"
"@mui/x-internals" "7.18.0"
"@types/react-transition-group" "^4.4.11"
clsx "^2.1.1"
prop-types "^15.8.1"
react-transition-group "^4.4.5"
"@mui/x-internals@7.18.0":
version "7.18.0"
resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-7.18.0.tgz#f079968d4f7ea93e63be9faf6ba8558d6f12923b"
integrity sha512-lzCHOWIR0cAIY1bGrWSprYerahbnH5C31ql/2OWCEjcngL2NAV1M6oKI2Vp4HheqzJ822c60UyWyapvyjSzY/A==
dependencies:
"@babel/runtime" "^7.25.6"
"@mui/utils" "^5.16.6"
"@nghedgehog/core@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@nghedgehog/core/-/core-0.0.4.tgz#4e3231847d0dac557a2e2dbdf1e3a52b106dd57c"
@ -2482,32 +2173,6 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
"@popperjs/core@^2.11.8":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@rc-component/portal@^1.1.0":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@rc-component/portal/-/portal-1.1.2.tgz#55db1e51d784e034442e9700536faaa6ab63fc71"
integrity sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==
dependencies:
"@babel/runtime" "^7.18.0"
classnames "^2.3.2"
rc-util "^5.24.4"
"@rc-component/trigger@^2.1.1":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@rc-component/trigger/-/trigger-2.2.3.tgz#b47e945115e2d0a7f7e067dbb9ed76c91c1b4385"
integrity sha512-X1oFIpKoXAMXNDYCviOmTfuNuYxE4h5laBsyCqVAVMjNHxoF3/uiyA7XdegK1XbCvBbCZ6P6byWrEoDRpKL8+A==
dependencies:
"@babel/runtime" "^7.23.2"
"@rc-component/portal" "^1.1.0"
classnames "^2.3.2"
rc-motion "^2.0.0"
rc-resize-observer "^1.3.1"
rc-util "^5.38.0"
"@rollup/rollup-android-arm-eabi@4.22.4":
version "4.22.4"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz#8b613b9725e8f9479d142970b106b6ae878610d5"
@ -3118,16 +2783,6 @@
dependencies:
undici-types "~6.19.2"
"@types/parse-json@^4.0.0":
version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==
"@types/prop-types@*", "@types/prop-types@^15.7.12", "@types/prop-types@^15.7.13":
version "15.7.13"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.13.tgz#2af91918ee12d9d32914feb13f5326658461b451"
integrity sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==
"@types/qs@*":
version "6.9.16"
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794"
@ -3143,28 +2798,6 @@
resolved "https://registry.yarnpkg.com/@types/raphael/-/raphael-2.3.9.tgz#d53bb8930431524f42987a8a19815c0d42a61eb5"
integrity sha512-K1dZwoLNvEN+mvleFU/t2swG9Z4SE5Vub7dA5wDYojH0bVTQ8ZAP+lNsl91t1njdu/B+roSEL4QXC67I7Hpiag==
"@types/react-dom@18.3.0":
version "18.3.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0"
integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.11":
version "4.4.11"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5"
integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@18.3.10":
version "18.3.10"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.10.tgz#6edc26dc22ff8c9c226d3c7bf8357b013c842219"
integrity sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/retry@0.12.2":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a"
@ -3470,7 +3103,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
mime-types "~2.1.34"
negotiator "0.6.3"
ace-builds@1.36.5, ace-builds@^1.32.8:
ace-builds@1.36.5:
version "1.36.5"
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.36.5.tgz#ae9cc7a32eccc2f484926131c00545cd6b78a6a6"
integrity sha512-mZ5KVanRT6nLRDLqtG/1YQQLX/gZVC/v526cm1Ru/MTSlrbweSmqv2ZT0d2GaHpJq035MwCMIrj+LgDAUnDXrg==
@ -3790,11 +3423,6 @@ at-least-node@^1.0.0:
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
attr-accept@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
autoprefixer@10.4.20, autoprefixer@^10.4.20:
version "10.4.20"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
@ -3827,15 +3455,6 @@ babel-loader@9.1.3:
find-cache-dir "^4.0.0"
schema-utils "^4.0.0"
babel-plugin-macros@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==
dependencies:
"@babel/runtime" "^7.12.5"
cosmiconfig "^7.0.0"
resolve "^1.19.0"
babel-plugin-polyfill-corejs2@^0.4.10:
version "0.4.11"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33"
@ -4129,11 +3748,6 @@ ci-info@^3.7.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
classnames@2.x, classnames@^2.2.1, classnames@^2.2.6, classnames@^2.3.2:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
clean-stack@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
@ -4212,11 +3826,6 @@ clone@^1.0.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==
clsx@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -4363,7 +3972,7 @@ content-type@~1.0.4, content-type@~1.0.5:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0:
convert-source-map@^1.5.1, convert-source-map@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
@ -4433,17 +4042,6 @@ cose-base@^2.2.0:
dependencies:
layout-base "^2.0.0"
cosmiconfig@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.2.1"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.10.0"
cosmiconfig@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d"
@ -4524,14 +4122,6 @@ css-select@^5.1.0:
domutils "^3.0.1"
nth-check "^2.0.1"
css-vendor@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
dependencies:
"@babel/runtime" "^7.8.3"
is-in-browser "^1.0.2"
css-what@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
@ -4542,11 +4132,6 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^3.0.2, csstype@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
cytoscape-cose-bilkent@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b"
@ -5062,14 +4647,6 @@ doctrine@^2.1.0:
dependencies:
esutils "^2.0.2"
dom-helpers@^5.0.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
dependencies:
"@babel/runtime" "^7.8.7"
csstype "^3.0.2"
dom-serializer@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
@ -5703,13 +5280,6 @@ file-entry-cache@^8.0.0:
dependencies:
flat-cache "^4.0.0"
file-selector@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc"
integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==
dependencies:
tslib "^2.4.0"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@ -5745,11 +5315,6 @@ find-replace@^3.0.0:
dependencies:
array-back "^3.0.1"
find-root@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
find-up@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@ -6126,13 +5691,6 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
hosted-git-info@^7.0.0:
version "7.0.2"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17"
@ -6267,11 +5825,6 @@ hyperdyperid@^1.2.0:
resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b"
integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==
hyphenate-style-name@^1.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -6511,11 +6064,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies:
is-extglob "^2.1.1"
is-in-browser@^1.0.2, is-in-browser@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
integrity sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==
is-inside-container@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4"
@ -6746,7 +6294,7 @@ js-cookie@^3.0.5:
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
@ -6861,76 +6409,6 @@ jsonparse@^1.3.1:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==
jss-plugin-camel-case@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz#27ea159bab67eb4837fa0260204eb7925d4daa1c"
integrity sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==
dependencies:
"@babel/runtime" "^7.3.1"
hyphenate-style-name "^1.0.3"
jss "10.10.0"
jss-plugin-default-unit@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.10.0.tgz#db3925cf6a07f8e1dd459549d9c8aadff9804293"
integrity sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.10.0"
jss-plugin-global@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.10.0.tgz#1c55d3c35821fab67a538a38918292fc9c567efd"
integrity sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.10.0"
jss-plugin-nested@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.10.0.tgz#db872ed8925688806e77f1fc87f6e62264513219"
integrity sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.10.0"
tiny-warning "^1.0.2"
jss-plugin-props-sort@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.10.0.tgz#67f4dd4c70830c126f4ec49b4b37ccddb680a5d7"
integrity sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.10.0"
jss-plugin-rule-value-function@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.10.0.tgz#7d99e3229e78a3712f78ba50ab342e881d26a24b"
integrity sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==
dependencies:
"@babel/runtime" "^7.3.1"
jss "10.10.0"
tiny-warning "^1.0.2"
jss-plugin-vendor-prefixer@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.10.0.tgz#c01428ef5a89f2b128ec0af87a314d0c767931c7"
integrity sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==
dependencies:
"@babel/runtime" "^7.3.1"
css-vendor "^2.0.8"
jss "10.10.0"
jss@10.10.0, jss@^10.10.0:
version "10.10.0"
resolved "https://registry.yarnpkg.com/jss/-/jss-10.10.0.tgz#a75cc85b0108c7ac8c7b7d296c520a3e4fbc6ccc"
integrity sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==
dependencies:
"@babel/runtime" "^7.3.1"
csstype "^3.0.2"
is-in-browser "^1.1.3"
tiny-warning "^1.0.2"
jstree-bootstrap-theme@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/jstree-bootstrap-theme/-/jstree-bootstrap-theme-1.0.1.tgz#7d5edc73a846e8da7f94f57a1cc5ddee9d9eab4b"
@ -7221,22 +6699,12 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@4.17.21, lodash@^4.0.1, lodash@^4.17.14:
lodash@4.17.21, lodash@^4.17.14:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -7265,13 +6733,6 @@ log-update@^6.1.0:
strip-ansi "^7.1.0"
wrap-ansi "^9.0.0"
loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^10.0.1, lru-cache@^10.2.0:
version "10.4.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
@ -7913,7 +7374,7 @@ nth-check@^2.0.1:
dependencies:
boolbase "^1.0.0"
object-assign@^4.0.1, object-assign@^4.1.1:
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@ -8185,7 +7646,7 @@ parse-imports@^2.1.1:
es-module-lexer "^1.5.3"
slashes "^3.0.12"
parse-json@^5.0.0, parse-json@^5.2.0:
parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@ -8292,11 +7753,6 @@ path-to-regexp@0.1.10:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
path-type@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8"
@ -8505,11 +7961,6 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@^2.8.3:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
prismjs@^1.27.0, prismjs@^1.28.0:
version "1.29.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12"
@ -8538,15 +7989,6 @@ promise-retry@^2.0.1:
err-code "^2.0.2"
retry "^0.12.0"
prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@ -8632,128 +8074,6 @@ rbush@^3.0.1:
dependencies:
quickselect "^2.0.0"
rc-motion@^2.0.0, rc-motion@^2.0.1:
version "2.9.3"
resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.9.3.tgz#b1bdaf816f1ccb3e4b3b0c531c3037a59286379e"
integrity sha512-rkW47ABVkic7WEB0EKJqzySpvDqwl60/tdkY7hWP7dYnh5pm0SzJpo54oW3TDUGXV5wfxXFmMkxrzRRbotQ0+w==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-util "^5.43.0"
rc-overflow@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/rc-overflow/-/rc-overflow-1.3.2.tgz#72ee49e85a1308d8d4e3bd53285dc1f3e0bcce2c"
integrity sha512-nsUm78jkYAoPygDAcGZeC2VwIg/IBGSodtOY3pMof4W3M9qRJgqaDYm03ZayHlde3I6ipliAxbN0RUcGf5KOzw==
dependencies:
"@babel/runtime" "^7.11.1"
classnames "^2.2.1"
rc-resize-observer "^1.0.0"
rc-util "^5.37.0"
rc-resize-observer@^1.0.0, rc-resize-observer@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.4.0.tgz#7bba61e6b3c604834980647cce6451914750d0cc"
integrity sha512-PnMVyRid9JLxFavTjeDXEXo65HCRqbmLBw9xX9gfC4BZiSzbLXKzW3jPz+J0P71pLbD5tBMTT+mkstV5gD0c9Q==
dependencies:
"@babel/runtime" "^7.20.7"
classnames "^2.2.1"
rc-util "^5.38.0"
resize-observer-polyfill "^1.5.1"
rc-select@14.15.2:
version "14.15.2"
resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.15.2.tgz#d85fcf3a708bdf837b003feeed653347b8980ad0"
integrity sha512-oNoXlaFmpqXYcQDzcPVLrEqS2J9c+/+oJuGrlXeVVX/gVgrbHa5YcyiRUXRydFjyuA7GP3elRuLF7Y3Tfwltlw==
dependencies:
"@babel/runtime" "^7.10.1"
"@rc-component/trigger" "^2.1.1"
classnames "2.x"
rc-motion "^2.0.1"
rc-overflow "^1.3.1"
rc-util "^5.16.1"
rc-virtual-list "^3.5.2"
rc-util@^5.15.0, rc-util@^5.16.1, rc-util@^5.24.4, rc-util@^5.37.0, rc-util@^5.38.0, rc-util@^5.43.0:
version "5.43.0"
resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.43.0.tgz#bba91fbef2c3e30ea2c236893746f3e9b05ecc4c"
integrity sha512-AzC7KKOXFqAdIBqdGWepL9Xn7cm3vnAmjlHqUnoQaTMZYhM4VlXGLkkHHxj/BZ7Td0+SOPKB4RGPboBVKT9htw==
dependencies:
"@babel/runtime" "^7.18.3"
react-is "^18.2.0"
rc-virtual-list@3.5.2, rc-virtual-list@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.5.2.tgz#5e1028869bae900eacbae6788d4eca7210736006"
integrity sha512-sE2G9hTPjVmatQni8OP2Kx33+Oth6DMKm67OblBBmgMBJDJQOOFpSGH7KZ6Pm85rrI2IGxDRXZCr0QhYOH2pfQ==
dependencies:
"@babel/runtime" "^7.20.0"
classnames "^2.2.6"
rc-resize-observer "^1.0.0"
rc-util "^5.15.0"
react-ace@12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-12.0.0.tgz#d40afc7382092109eead7227d9426f55dcc2209d"
integrity sha512-PstU6CSMfYIJknb4su2Fa0WgLXzq2ufQgR6fjcSWuGT1hGTHkBzuKw+SncV8PuLCdSJBJc1VehPhyeXlWByG/g==
dependencies:
ace-builds "^1.32.8"
diff-match-patch "^1.0.5"
lodash.get "^4.4.2"
lodash.isequal "^4.5.0"
prop-types "^15.8.1"
react-dom@18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-dropzone@14.2.9:
version "14.2.9"
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.9.tgz#193a33f9035e29fc91abf24e50de5d66cfa7c8c0"
integrity sha512-jRZsMC7h48WONsOLHcmhyn3cRWJoIPQjPApvt/sJVfnYaB3Qltn025AoRTTJaj4WdmmgmLl6tUQg1s0wOhpodQ==
dependencies:
attr-accept "^2.2.2"
file-selector "^0.6.0"
prop-types "^15.8.1"
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^18.2.0, react-is@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
loose-envify "^1.4.0"
prop-types "^15.6.2"
react@18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
dependencies:
loose-envify "^1.1.0"
reactcss@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd"
integrity sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==
dependencies:
lodash "^4.0.1"
read-cache@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
@ -8888,11 +8208,6 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@ -8909,7 +8224,7 @@ resolve-url-loader@5.0.0:
postcss "^8.2.14"
source-map "0.6.1"
resolve@1.22.8, resolve@^1.1.7, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.4, resolve@^1.22.8:
resolve@1.22.8, resolve@^1.1.7, resolve@^1.14.2, resolve@^1.22.4, resolve@^1.22.8:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@ -9092,13 +8407,6 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f"
integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==
scheduler@^0.23.2:
version "0.23.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
dependencies:
loose-envify "^1.1.0"
schema-inspector@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/schema-inspector/-/schema-inspector-2.1.0.tgz#85096fbc78162a420262ed41b82e60ac927767b2"
@ -9408,11 +8716,6 @@ source-map@0.7.4:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
source-map@^0.5.7:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
spdx-correct@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
@ -9621,11 +8924,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
stylis@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
stylis@^4.3.1:
version "4.3.4"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4"
@ -9810,11 +9108,6 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tiny-warning@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinycolor2@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
@ -10504,11 +9797,6 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.10.0:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.2.2, yaml@^2.3.4:
version "2.6.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.1.tgz#42f2b1ba89203f374609572d5349fb8686500773"

Loading…
Cancel
Save