@journeyapps-labs/reactor-mod-data-browser 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/@types/actions/schema-model/ViewSchemaModelAsJsonAction.d.ts +9 -0
  3. package/dist/@types/core/AbstractConnection.d.ts +2 -0
  4. package/dist/@types/core/SchemaModelDefinition.d.ts +4 -1
  5. package/dist/@types/core/SchemaModelObject.d.ts +15 -8
  6. package/dist/@types/core/query/AbstractQuery.d.ts +5 -10
  7. package/dist/@types/core/query/AbstractSerializableQuery.d.ts +11 -0
  8. package/dist/@types/core/query/Page.d.ts +2 -7
  9. package/dist/@types/core/query/query-changed/ChangedModelQuery.d.ts +15 -0
  10. package/dist/@types/core/query/query-simple/SimplePage.d.ts +12 -0
  11. package/dist/@types/core/query/{SimpleQuery.d.ts → query-simple/SimpleQuery.d.ts} +8 -8
  12. package/dist/@types/core/query/widgets/BelongsToDisplayWidget.d.ts +1 -3
  13. package/dist/@types/core/query/widgets/CellDisplayWidget.d.ts +1 -2
  14. package/dist/@types/core/query/widgets/ColumnDisplayWidget.d.ts +1 -0
  15. package/dist/@types/core/query/widgets/SmartBelongsToDisplayWidget.d.ts +10 -0
  16. package/dist/@types/core/query/widgets/SmartCellDisplayWidget.d.ts +7 -0
  17. package/dist/@types/core/query/widgets/SmartColumnWidget.d.ts +2 -1
  18. package/dist/@types/entities/QueryEntityDefinition.d.ts +3 -3
  19. package/dist/@types/forms/SchemaModelForm.d.ts +32 -1
  20. package/dist/@types/forms/TypeEngine.d.ts +31 -0
  21. package/dist/@types/forms/inputs/DirtyWrapperInput.d.ts +16 -0
  22. package/dist/@types/index.d.ts +3 -1
  23. package/dist/@types/panels/_shared/SharedModelPanelFactory.d.ts +22 -0
  24. package/dist/@types/panels/model/ModelPanelFactory.d.ts +4 -20
  25. package/dist/@types/panels/model-json/ModelJsonPanelFactory.d.ts +27 -0
  26. package/dist/@types/panels/model-json/ModelJsonPanelWidget.d.ts +6 -0
  27. package/dist/@types/panels/query/QueryPanelFactory.d.ts +2 -1
  28. package/dist/DataBrowserModule.js +6 -0
  29. package/dist/DataBrowserModule.js.map +1 -1
  30. package/dist/actions/schema-definitions/CreateModelAction.js +2 -1
  31. package/dist/actions/schema-definitions/CreateModelAction.js.map +1 -1
  32. package/dist/actions/schema-definitions/QuerySchemaModelAction.js +1 -1
  33. package/dist/actions/schema-definitions/QuerySchemaModelAction.js.map +1 -1
  34. package/dist/actions/schema-model/ViewSchemaModelAsJsonAction.js +46 -0
  35. package/dist/actions/schema-model/ViewSchemaModelAsJsonAction.js.map +1 -0
  36. package/dist/core/AbstractConnection.js +15 -0
  37. package/dist/core/AbstractConnection.js.map +1 -1
  38. package/dist/core/SchemaModelDefinition.js +52 -13
  39. package/dist/core/SchemaModelDefinition.js.map +1 -1
  40. package/dist/core/SchemaModelObject.js +90 -50
  41. package/dist/core/SchemaModelObject.js.map +1 -1
  42. package/dist/core/query/AbstractQuery.js +2 -8
  43. package/dist/core/query/AbstractQuery.js.map +1 -1
  44. package/dist/core/query/AbstractSerializableQuery.js +13 -0
  45. package/dist/core/query/AbstractSerializableQuery.js.map +1 -0
  46. package/dist/core/query/Page.js +6 -15
  47. package/dist/core/query/Page.js.map +1 -1
  48. package/dist/core/query/query-changed/ChangedModelQuery.js +31 -0
  49. package/dist/core/query/query-changed/ChangedModelQuery.js.map +1 -0
  50. package/dist/core/query/query-simple/SimplePage.js +18 -0
  51. package/dist/core/query/query-simple/SimplePage.js.map +1 -0
  52. package/dist/core/query/{SimpleQuery.js → query-simple/SimpleQuery.js} +26 -25
  53. package/dist/core/query/query-simple/SimpleQuery.js.map +1 -0
  54. package/dist/core/query/widgets/BelongsToDisplayWidget.js +5 -16
  55. package/dist/core/query/widgets/BelongsToDisplayWidget.js.map +1 -1
  56. package/dist/core/query/widgets/CellDisplayWidget.js +16 -76
  57. package/dist/core/query/widgets/CellDisplayWidget.js.map +1 -1
  58. package/dist/core/query/widgets/ColumnDisplayWidget.js +3 -3
  59. package/dist/core/query/widgets/ColumnDisplayWidget.js.map +1 -1
  60. package/dist/core/query/widgets/SmartBelongsToDisplayWidget.js +29 -0
  61. package/dist/core/query/widgets/SmartBelongsToDisplayWidget.js.map +1 -0
  62. package/dist/core/query/widgets/SmartCellDisplayWidget.js +20 -0
  63. package/dist/core/query/widgets/SmartCellDisplayWidget.js.map +1 -0
  64. package/dist/core/query/widgets/SmartColumnWidget.js +11 -1
  65. package/dist/core/query/widgets/SmartColumnWidget.js.map +1 -1
  66. package/dist/entities/ConnectionEntityDefinition.js +5 -0
  67. package/dist/entities/ConnectionEntityDefinition.js.map +1 -1
  68. package/dist/entities/QueryEntityDefinition.js +3 -3
  69. package/dist/entities/QueryEntityDefinition.js.map +1 -1
  70. package/dist/entities/SchemaModelDefinitionEntityDefinition.js +3 -1
  71. package/dist/entities/SchemaModelDefinitionEntityDefinition.js.map +1 -1
  72. package/dist/entities/SchemaModelObjectEntityDefinition.js +28 -8
  73. package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
  74. package/dist/forms/SchemaModelForm.js +153 -74
  75. package/dist/forms/SchemaModelForm.js.map +1 -1
  76. package/dist/forms/TypeEngine.js +353 -0
  77. package/dist/forms/TypeEngine.js.map +1 -0
  78. package/dist/forms/inputs/DirtyWrapperInput.js +86 -0
  79. package/dist/forms/inputs/DirtyWrapperInput.js.map +1 -0
  80. package/dist/forms/inputs/LocationInput.js +13 -7
  81. package/dist/forms/inputs/LocationInput.js.map +1 -1
  82. package/dist/index.js +3 -1
  83. package/dist/index.js.map +1 -1
  84. package/dist/panels/_shared/SharedModelPanelFactory.js +77 -0
  85. package/dist/panels/_shared/SharedModelPanelFactory.js.map +1 -0
  86. package/dist/panels/model/ModelPanelFactory.js +7 -71
  87. package/dist/panels/model/ModelPanelFactory.js.map +1 -1
  88. package/dist/panels/model/ModelPanelWidget.js +21 -4
  89. package/dist/panels/model/ModelPanelWidget.js.map +1 -1
  90. package/dist/panels/model-json/ModelJsonPanelFactory.js +62 -0
  91. package/dist/panels/model-json/ModelJsonPanelFactory.js.map +1 -0
  92. package/dist/panels/model-json/ModelJsonPanelWidget.js +70 -0
  93. package/dist/panels/model-json/ModelJsonPanelWidget.js.map +1 -0
  94. package/dist/panels/query/QueryPanelFactory.js +4 -0
  95. package/dist/panels/query/QueryPanelFactory.js.map +1 -1
  96. package/dist/panels/query/TableControlsWidget.js +42 -9
  97. package/dist/panels/query/TableControlsWidget.js.map +1 -1
  98. package/dist/tsconfig.tsbuildinfo +1 -1
  99. package/dist-module/bundle.js +91 -41
  100. package/dist-module/bundle.js.map +1 -1
  101. package/package.json +13 -10
  102. package/src/DataBrowserModule.ts +6 -0
  103. package/src/actions/schema-definitions/CreateModelAction.ts +2 -1
  104. package/src/actions/schema-definitions/QuerySchemaModelAction.ts +1 -1
  105. package/src/actions/schema-model/ViewSchemaModelAsJsonAction.ts +33 -0
  106. package/src/core/AbstractConnection.ts +18 -1
  107. package/src/core/SchemaModelDefinition.ts +57 -14
  108. package/src/core/SchemaModelObject.ts +62 -46
  109. package/src/core/query/AbstractQuery.ts +10 -21
  110. package/src/core/query/AbstractSerializableQuery.ts +23 -0
  111. package/src/core/query/Page.ts +6 -21
  112. package/src/core/query/query-changed/ChangedModelQuery.ts +35 -0
  113. package/src/core/query/query-simple/SimplePage.ts +29 -0
  114. package/src/core/query/{SimpleQuery.tsx → query-simple/SimpleQuery.tsx} +41 -39
  115. package/src/core/query/widgets/BelongsToDisplayWidget.tsx +6 -19
  116. package/src/core/query/widgets/CellDisplayWidget.tsx +17 -97
  117. package/src/core/query/widgets/ColumnDisplayWidget.tsx +12 -3
  118. package/src/core/query/widgets/SmartBelongsToDisplayWidget.tsx +46 -0
  119. package/src/core/query/widgets/SmartCellDisplayWidget.tsx +29 -0
  120. package/src/core/query/widgets/SmartColumnWidget.tsx +18 -2
  121. package/src/entities/ConnectionEntityDefinition.tsx +5 -0
  122. package/src/entities/QueryEntityDefinition.ts +6 -6
  123. package/src/entities/SchemaModelDefinitionEntityDefinition.ts +4 -0
  124. package/src/entities/SchemaModelObjectEntityDefinition.ts +34 -8
  125. package/src/forms/SchemaModelForm.tsx +145 -96
  126. package/src/forms/TypeEngine.tsx +451 -0
  127. package/src/forms/inputs/DirtyWrapperInput.tsx +95 -0
  128. package/src/forms/inputs/LocationInput.tsx +14 -7
  129. package/src/index.ts +7 -1
  130. package/src/panels/_shared/SharedModelPanelFactory.tsx +54 -0
  131. package/src/panels/model/ModelPanelFactory.tsx +10 -49
  132. package/src/panels/model/ModelPanelWidget.tsx +45 -9
  133. package/src/panels/model-json/ModelJsonPanelFactory.tsx +61 -0
  134. package/src/panels/model-json/ModelJsonPanelWidget.tsx +92 -0
  135. package/src/panels/query/QueryPanelFactory.tsx +5 -0
  136. package/src/panels/query/TableControlsWidget.tsx +85 -17
  137. package/webpack.config.js +9 -2
  138. package/dist/core/query/SimpleQuery.js.map +0 -1
@@ -1,116 +1,165 @@
1
- import {
2
- BooleanInput,
3
- DateInput,
4
- DateTimePickerType,
5
- FileInput,
6
- FormModel,
7
- ImageInput,
8
- ImageMedia,
9
- SelectInput,
10
- TextInput
11
- } from '@journeyapps-labs/reactor-mod';
1
+ import { EntityInput, FormInput, FormModel, inject } from '@journeyapps-labs/reactor-mod';
12
2
  import { SchemaModelDefinition } from '../core/SchemaModelDefinition';
13
3
  import { SchemaModelObject } from '../core/SchemaModelObject';
14
4
  import * as _ from 'lodash';
15
- import {
16
- AttachmentType,
17
- BooleanType,
18
- DatetimeType,
19
- DateType,
20
- LocationType,
21
- MultipleChoiceType,
22
- PhotoType,
23
- SignatureType,
24
- SingleChoiceIntegerType,
25
- SingleChoiceType,
26
- TextType
27
- } from '@journeyapps/db';
28
- import { LocationInput } from './inputs/LocationInput';
5
+ import { DataBrowserEntities } from '../entities';
6
+ import { DirtyWrapperInput } from './inputs/DirtyWrapperInput';
7
+ import { TypeEngine, TypeHandler } from './TypeEngine';
8
+ import { autorun, IReactionDisposer } from 'mobx';
9
+ import { Variable } from '@journeyapps/db';
29
10
 
30
11
  export interface SchemaModelFormOptions {
31
12
  definition: SchemaModelDefinition;
32
13
  object?: SchemaModelObject;
33
14
  }
34
15
 
35
- export class SchemaModelForm extends FormModel {
36
- constructor(protected options: SchemaModelFormOptions) {
37
- super();
16
+ export interface BindingOption {
17
+ input: FormInput;
18
+ name: string;
19
+ model: SchemaModelObject;
20
+ resolve: () => Promise<any>;
21
+ }
38
22
 
39
- _.map(options.definition.definition.attributes, (attribute) => {
40
- if (attribute.type instanceof DatetimeType) {
41
- return new DateInput({
42
- name: attribute.name,
43
- label: attribute.label,
44
- value: options.object?.model[attribute.name] || null,
45
- type: DateTimePickerType.DATETIME
46
- });
47
- }
48
- if (attribute.type instanceof DateType) {
49
- return new DateInput({
50
- name: attribute.name,
51
- label: attribute.label,
52
- value: options.object?.model[attribute.name] || null,
53
- type: DateTimePickerType.DATE
54
- });
55
- }
56
- if (attribute.type instanceof SignatureType || attribute.type instanceof PhotoType) {
57
- let media = new ImageInput({
58
- name: attribute.name,
59
- label: attribute.label
60
- });
61
-
62
- if (options.object) {
63
- options.object.getMedia(attribute.name).then((m) => {
64
- media.setValue(m as ImageMedia);
65
- });
23
+ export class Binding {
24
+ @inject(TypeEngine)
25
+ accessor typeEngine: TypeEngine;
26
+
27
+ setting_value_via_autorun: boolean;
28
+
29
+ private listener1: IReactionDisposer;
30
+ private listener2: () => any;
31
+
32
+ constructor(protected options: BindingOption) {
33
+ const { input, model, name } = options;
34
+ this.setting_value_via_autorun = false;
35
+ this.listener2 = input.registerListener({
36
+ valueChanged: async () => {
37
+ if (this.setting_value_via_autorun) {
38
+ return;
66
39
  }
67
- return media;
68
- }
69
- if (attribute.type instanceof AttachmentType) {
70
- return new FileInput({
71
- name: attribute.name,
72
- label: attribute.label,
73
- value: options.object?.model[attribute.name] || null
74
- });
75
- }
76
- if (attribute.type instanceof BooleanType) {
77
- return new BooleanInput({
78
- name: attribute.name,
79
- label: attribute.label,
80
- value: options.object?.model[attribute.name]
81
- });
82
- }
83
- if (attribute.type instanceof LocationType) {
84
- return new LocationInput({
85
- name: attribute.name,
86
- label: attribute.label,
87
- value: options.object?.model[attribute.name]
88
- });
40
+ model.set(input.name, await this.encode(input.value));
89
41
  }
90
- if (attribute.type instanceof SingleChoiceIntegerType || attribute.type instanceof SingleChoiceType) {
91
- return new SelectInput({
92
- name: attribute.name,
93
- label: attribute.label,
94
- value: options.object?.model[attribute.name],
95
- options: _.mapValues(attribute.type.options, (option) => {
96
- return `${option.value}`;
97
- })
98
- });
42
+ });
43
+ this.listener1 = autorun(async () => {
44
+ let value = model.patch.get(name);
45
+ if (!model.patch.has(name)) {
46
+ value = await options.resolve();
99
47
  }
100
- if (attribute.type instanceof MultipleChoiceType) {
48
+ this.setting_value_via_autorun = true;
49
+ if (value == null) {
50
+ input.setValue(null);
51
+ } else {
52
+ let decoded = await this.decode(value);
53
+ input.setValue(decoded);
101
54
  }
102
- if (attribute.type instanceof TextType) {
103
- return new TextInput({
104
- name: attribute.name,
105
- label: attribute.label,
106
- value: options.object?.model[attribute.name]
107
- });
55
+ this.setting_value_via_autorun = false;
56
+ });
57
+ }
58
+
59
+ async encode(value) {
60
+ return value;
61
+ }
62
+
63
+ async decode(value) {
64
+ return value;
65
+ }
66
+
67
+ get input() {
68
+ return this.options.input;
69
+ }
70
+
71
+ dispose() {
72
+ this.listener1();
73
+ this.listener2();
74
+ }
75
+ }
76
+
77
+ export class TypedBinding extends Binding {
78
+ handler: TypeHandler;
79
+ constructor(options: Omit<BindingOption & { variable: Variable }, 'resolve'>) {
80
+ super({
81
+ ...options,
82
+ resolve: async () => {
83
+ return options.model?.model[options.name];
108
84
  }
109
- return null;
85
+ });
86
+ this.handler = this.typeEngine.getHandler(options.variable.type);
87
+ }
88
+
89
+ async encode(value) {
90
+ return await this.handler.encode(value);
91
+ }
92
+
93
+ async decode(value) {
94
+ return await this.handler.decode(value);
95
+ }
96
+ }
97
+
98
+ export class SchemaModelForm extends FormModel {
99
+ @inject(TypeEngine)
100
+ accessor typeEngine: TypeEngine;
101
+
102
+ bindings: Set<Binding>;
103
+
104
+ constructor(protected options: SchemaModelFormOptions) {
105
+ super();
106
+ this.bindings = new Set<Binding>();
107
+ _.map(options.definition.definition.belongsTo, (relationship) => {
108
+ const definition = options.definition.connection.getSchemaModelDefinitionByName(relationship.foreignType.name);
109
+
110
+ let entity = new EntityInput({
111
+ name: relationship.name,
112
+ entityType: DataBrowserEntities.SCHEMA_MODEL_OBJECT,
113
+ parent: definition,
114
+ label: relationship.name,
115
+ value: null
116
+ });
117
+
118
+ return new Binding({
119
+ name: relationship.name,
120
+ model: this.options.object,
121
+ input: entity,
122
+ resolve: () => {
123
+ if (!options.object.data.belongs_to[relationship.name]) {
124
+ return null;
125
+ }
126
+ return definition.resolve(options.object.data.belongs_to[relationship.name]);
127
+ }
128
+ });
110
129
  })
111
130
  .filter((f) => !!f)
112
- .forEach((a) => {
113
- this.addInput(a);
131
+ .forEach((binding) => {
132
+ this.bindings.add(binding);
133
+ this.addInput(new DirtyWrapperInput(binding.input, options.object));
134
+ });
135
+
136
+ _.map(options.definition.definition.attributes, (attribute) => {
137
+ let field = this.typeEngine.getHandler(attribute.type)?.generateField({
138
+ name: attribute.name,
139
+ label: attribute.label,
140
+ type: attribute.type
141
+ });
142
+ if (!field) {
143
+ return;
144
+ }
145
+
146
+ return new TypedBinding({
147
+ variable: attribute,
148
+ model: this.options.object,
149
+ input: field,
150
+ name: attribute.name
151
+ });
152
+ })
153
+ .filter((binding) => !!binding)
154
+ .forEach((binding) => {
155
+ this.bindings.add(binding);
156
+ this.addInput(new DirtyWrapperInput(binding.input, options.object));
114
157
  });
115
158
  }
159
+
160
+ dispose() {
161
+ this.bindings.forEach((b) => {
162
+ b.dispose();
163
+ });
164
+ }
116
165
  }
@@ -0,0 +1,451 @@
1
+ import {
2
+ Attachment,
3
+ AttachmentType,
4
+ BooleanType,
5
+ DatetimeType,
6
+ DateType,
7
+ Day,
8
+ Location,
9
+ LocationType,
10
+ MultipleChoiceIntegerType,
11
+ MultipleChoiceType,
12
+ NumberType,
13
+ PhotoType,
14
+ SignatureType,
15
+ SingleChoiceIntegerType,
16
+ SingleChoiceType,
17
+ TextType,
18
+ Type
19
+ } from '@journeyapps/db';
20
+ import {
21
+ AbstractMedia,
22
+ BooleanInput,
23
+ CheckboxWidget,
24
+ DateInput,
25
+ DateTimePickerType,
26
+ FileInput,
27
+ FormInput,
28
+ ImageInput,
29
+ ImageMedia,
30
+ inject,
31
+ MediaEngine,
32
+ MetadataWidget,
33
+ NumberInput,
34
+ PanelButtonWidget,
35
+ SelectInput,
36
+ SmartDateDisplayWidget,
37
+ styled,
38
+ TableButtonWidget,
39
+ TextAreaInput,
40
+ TextInput,
41
+ TextInputType,
42
+ MultiSelectInput,
43
+ WorkspaceStore
44
+ } from '@journeyapps-labs/reactor-mod';
45
+ import {} from '@journeyapps-labs/reactor-mod-editor';
46
+ import { LocationInput } from './inputs/LocationInput';
47
+ import * as React from 'react';
48
+ import { JSX } from 'react';
49
+ import { SchemaModelObject } from '../core/SchemaModelObject';
50
+ import * as _ from 'lodash';
51
+ import { ModelJsonPanelModel } from '../panels/model-json/ModelJsonPanelFactory';
52
+
53
+ const MAX_NUMBER_OF_ARR_ITEMS_TO_DISPLAY = 3;
54
+
55
+ export interface TypeHandler<T extends Type = Type, ENCODED = any, DECODED = any> {
56
+ matches: (type: Type) => boolean;
57
+ generateField: (event: { label: string; name: string; type: T }) => FormInput;
58
+ generateDisplay: (event: {
59
+ label: string;
60
+ name: string;
61
+ type: T;
62
+ value: ENCODED;
63
+ model: SchemaModelObject;
64
+ }) => JSX.Element | string;
65
+ decode: (value: ENCODED) => Promise<DECODED>;
66
+ encode: (value: DECODED) => Promise<ENCODED>;
67
+ }
68
+
69
+ export class TypeEngine {
70
+ handlers: Set<TypeHandler>;
71
+
72
+ @inject(MediaEngine)
73
+ accessor mediaEngine: MediaEngine;
74
+
75
+ @inject(WorkspaceStore)
76
+ accessor workspaceStore: WorkspaceStore;
77
+
78
+ private _mediaCache: Map<string, AbstractMedia>;
79
+
80
+ constructor() {
81
+ this.handlers = new Set();
82
+ this._mediaCache = new Map();
83
+ this.register({
84
+ matches: (type) => type instanceof DatetimeType || type instanceof DateType,
85
+ encode: async (value: Date) => new Day(value),
86
+ decode: async (value: Day | Date) => {
87
+ if (value instanceof Day) {
88
+ return value.toDate();
89
+ }
90
+ return value;
91
+ },
92
+ generateField: ({ label, name, type }) => {
93
+ return new DateInput({
94
+ name,
95
+ label,
96
+ type: type instanceof DatetimeType ? DateTimePickerType.DATETIME : DateTimePickerType.DATE
97
+ });
98
+ },
99
+ generateDisplay: ({ value }) => {
100
+ if (value instanceof Day) {
101
+ return <SmartDateDisplayWidget date={value.toDate()} />;
102
+ }
103
+ return <SmartDateDisplayWidget date={value} />;
104
+ }
105
+ });
106
+
107
+ this.register({
108
+ matches: (type) => type instanceof SignatureType || type instanceof PhotoType,
109
+ encode: async (value: ImageMedia) => {
110
+ return Attachment.create({
111
+ data: await value.toArrayBuffer()
112
+ });
113
+ },
114
+ decode: async (value: Attachment) => {
115
+ if (this._mediaCache.has(value.id)) {
116
+ return this._mediaCache.get(value.id);
117
+ }
118
+ let media = this.mediaEngine.getMediaTypeForPath('.jpg').generateMedia({
119
+ content: await value.toArrayBuffer(),
120
+ name: value.id,
121
+ uid: value.id
122
+ });
123
+
124
+ this._mediaCache.set(value.id, media);
125
+ return media;
126
+ },
127
+ generateField: ({ label, name }) => {
128
+ return new ImageInput({
129
+ name,
130
+ label
131
+ });
132
+ },
133
+ generateDisplay: ({ value, type }) => {
134
+ if (value.uploaded()) {
135
+ return (
136
+ <S.Preview
137
+ onClick={() => {
138
+ this.getHandler(type)
139
+ .decode(value)
140
+ .then((media: ImageMedia) => {
141
+ if (media instanceof ImageMedia) {
142
+ media.open();
143
+ } else {
144
+ window.open(value.url(), '_blank');
145
+ }
146
+ });
147
+ }}
148
+ src={value.urls['thumbnail']}
149
+ />
150
+ );
151
+ }
152
+ return <S.Empty>Not uploaded</S.Empty>;
153
+ }
154
+ });
155
+
156
+ this.register({
157
+ matches: (type) => type instanceof AttachmentType,
158
+ encode: async (value: File) => {
159
+ return Attachment.create({
160
+ data: await value.arrayBuffer(),
161
+ filename: value.name
162
+ });
163
+ },
164
+ decode: async (value: Attachment) => {
165
+ return new File([await value.toArrayBuffer()], value.id);
166
+ },
167
+ generateField: ({ label, name }) => {
168
+ return new FileInput({
169
+ name,
170
+ label
171
+ });
172
+ },
173
+ generateDisplay: ({ value }) => {
174
+ return (
175
+ <TableButtonWidget
176
+ icon="download"
177
+ action={() => {
178
+ window.open(value.url(), '_blank');
179
+ }}
180
+ />
181
+ );
182
+ }
183
+ });
184
+ this.register({
185
+ matches: (type) => type instanceof BooleanType,
186
+ encode: async (value: boolean) => value,
187
+ decode: async (value: boolean) => value,
188
+ generateField: ({ label, name }) => {
189
+ return new BooleanInput({
190
+ name,
191
+ label
192
+ });
193
+ },
194
+ generateDisplay: ({ value, type, name, model }) => {
195
+ return (
196
+ <CheckboxWidget
197
+ checked={value}
198
+ onChange={(checked) => {
199
+ model.set(name, checked);
200
+ }}
201
+ />
202
+ );
203
+ }
204
+ });
205
+
206
+ this.register({
207
+ matches: (type) => type instanceof NumberType,
208
+ encode: async (value: number) => value,
209
+ decode: async (value: number) => value,
210
+ generateField: ({ label, name }) => {
211
+ return new NumberInput({
212
+ name,
213
+ label
214
+ });
215
+ },
216
+ generateDisplay: ({ value, type, name, model }) => {
217
+ return `${value}`;
218
+ }
219
+ });
220
+
221
+ this.register<TextType, string, string>({
222
+ matches: (type) => type instanceof TextType,
223
+ encode: async (value: string) => value,
224
+ decode: async (value: string) => value,
225
+ generateField: ({ label, name, type }) => {
226
+ if (type.subType == 'paragraph') {
227
+ return new TextAreaInput({
228
+ name,
229
+ label
230
+ });
231
+ }
232
+
233
+ if (type.subType == 'password') {
234
+ return new TextInput({
235
+ name,
236
+ label,
237
+ inputType: TextInputType.PASSWORD
238
+ });
239
+ }
240
+
241
+ return new TextInput({
242
+ name,
243
+ label
244
+ });
245
+ },
246
+ generateDisplay: ({ value, type, name, model }) => {
247
+ if (value.trim() === '') {
248
+ return <S.Empty>empty</S.Empty>;
249
+ }
250
+
251
+ if (type.subType == 'password') {
252
+ return '****';
253
+ }
254
+
255
+ if (type.subType == 'paragraph') {
256
+ // could be JSON
257
+ if ((value.startsWith('[') && value.endsWith(']')) || (value.startsWith('{') && value.endsWith('}'))) {
258
+ try {
259
+ let parsed = JSON.parse(value);
260
+
261
+ return (
262
+ <TableButtonWidget
263
+ icon="code"
264
+ label="JSON"
265
+ action={() => {
266
+ this.workspaceStore.addModel(
267
+ new ModelJsonPanelModel({
268
+ definition: model.definition,
269
+ model: model,
270
+ field: name
271
+ })
272
+ );
273
+ }}
274
+ />
275
+ );
276
+ } catch (ex) {}
277
+ }
278
+ }
279
+
280
+ if (type.subType == 'url') {
281
+ return (
282
+ <S.Container>
283
+ {value}
284
+ <TableButtonWidget
285
+ icon="arrow-right"
286
+ action={() => {
287
+ window.open(value, '_blank');
288
+ }}
289
+ />
290
+ </S.Container>
291
+ );
292
+ }
293
+
294
+ return <S.Max>{value}</S.Max>;
295
+ }
296
+ });
297
+ this.register({
298
+ matches: (type) => type instanceof LocationType,
299
+ encode: async (value: Location) => value,
300
+ decode: async (value: Location) => value,
301
+ generateField: ({ label, name }) => {
302
+ return new LocationInput({
303
+ name,
304
+ label
305
+ });
306
+ },
307
+ generateDisplay: ({ value }) => {
308
+ return (
309
+ <>
310
+ <MetadataWidget label={'Lat'} value={`${value.latitude}`} />
311
+ <MetadataWidget label={'Long'} value={`${value.longitude}`} />
312
+ </>
313
+ );
314
+ }
315
+ });
316
+
317
+ this.register({
318
+ matches: (type) => type instanceof SingleChoiceIntegerType,
319
+ encode: async (value: string) => parseInt(value),
320
+ decode: async (value: number) => `${value}`,
321
+ generateField: ({ label, name, type }) => {
322
+ return new SelectInput({
323
+ name,
324
+ label,
325
+ options: _.mapValues(type.options, (o) => `${o.value}`)
326
+ });
327
+ },
328
+ generateDisplay: ({ value, type, name, model, label }) => {
329
+ return `${value}`;
330
+ }
331
+ });
332
+
333
+ this.register({
334
+ matches: (type) => type instanceof SingleChoiceType,
335
+ encode: async (value: string) => value,
336
+ decode: async (value: string) => value,
337
+ generateField: ({ label, name, type }) => {
338
+ return new SelectInput({
339
+ name,
340
+ label,
341
+ options: _.mapValues(type.options, (o) => `${o.value}`)
342
+ });
343
+ },
344
+ generateDisplay: ({ value }) => {
345
+ return value;
346
+ }
347
+ });
348
+
349
+ this.register({
350
+ matches: (type) => type instanceof MultipleChoiceType,
351
+ encode: async (value: string[]) => value,
352
+ decode: async (value: string[]) => value,
353
+ generateField: ({ label, name, type }) => {
354
+ return new MultiSelectInput({
355
+ name,
356
+ label,
357
+ options: _.mapValues(type.options, (o) => `${o.value}`)
358
+ });
359
+ },
360
+ generateDisplay: ({ value }) => {
361
+ return this.displayArray(value);
362
+ }
363
+ });
364
+
365
+ this.register({
366
+ matches: (type) => type instanceof MultipleChoiceIntegerType,
367
+ encode: async (value: string[]) => value.map((v) => parseInt(v)),
368
+ decode: async (value: number[]) => value.map((v) => `${v}`),
369
+ generateField: ({ label, name, type }) => {
370
+ return new MultiSelectInput({
371
+ name,
372
+ label,
373
+ options: _.mapValues(type.options, (o) => `${o.value}`)
374
+ });
375
+ },
376
+ generateDisplay: ({ value }) => {
377
+ return this.displayArray(value);
378
+ }
379
+ });
380
+ }
381
+
382
+ displayArray(value: any[]) {
383
+ if (value.length === 0) {
384
+ return <S.Empty>empty array</S.Empty>;
385
+ }
386
+ let items = _.slice(value, 0, MAX_NUMBER_OF_ARR_ITEMS_TO_DISPLAY);
387
+ return (
388
+ <S.Pills>
389
+ {items.map((c) => {
390
+ return <S.pill key={c}>{c}</S.pill>;
391
+ })}
392
+ {items.length !== value.length ? '...' : null}
393
+ </S.Pills>
394
+ );
395
+ }
396
+
397
+ getHandler(type: Type) {
398
+ for (let handler of this.handlers) {
399
+ if (handler.matches(type)) {
400
+ return handler;
401
+ }
402
+ }
403
+ return null;
404
+ }
405
+
406
+ register<T extends Type = Type, ENCODED = any, DECODED = any>(handler: TypeHandler<T, ENCODED, DECODED>) {
407
+ this.handlers.add(handler);
408
+ }
409
+ }
410
+
411
+ namespace S {
412
+ export const Preview = styled.img`
413
+ max-height: 40px;
414
+ max-width: 40px;
415
+ cursor: pointer;
416
+ `;
417
+
418
+ export const Empty = styled.div`
419
+ opacity: 0.2;
420
+ `;
421
+
422
+ export const Max = styled.div`
423
+ max-width: 500px;
424
+ white-space: pre;
425
+ display: inline;
426
+ overflow: hidden;
427
+ text-overflow: ellipsis;
428
+ `;
429
+
430
+ export const Container = styled.div`
431
+ display: flex;
432
+ flex-direction: row;
433
+ column-gap: 5px;
434
+ align-items: center;
435
+ justify-content: space-between;
436
+ flex-grow: 1;
437
+ `;
438
+
439
+ export const pill = styled.div`
440
+ padding: 2px 4px;
441
+ background: ${(p) => p.theme.table.pills};
442
+ border-radius: 3px;
443
+ font-size: 12px;
444
+ `;
445
+
446
+ export const Pills = styled.div`
447
+ display: flex;
448
+ column-gap: 2px;
449
+ row-gap: 2px;
450
+ `;
451
+ }