mxrb 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.de-DE.md +108 -0
- data/README.md +172 -0
- data/README.pt-BR.md +109 -0
- data/bin/mxrb +1613 -0
- data/docs/README.md +10 -0
- data/docs/de-DE/README.md +24 -0
- data/docs/de-DE/architectural-patterns.md +26 -0
- data/docs/de-DE/architectural-standard.md +48 -0
- data/docs/de-DE/architecture.md +76 -0
- data/docs/de-DE/compiler.md +162 -0
- data/docs/de-DE/conventions.md +19 -0
- data/docs/de-DE/design-system.md +52 -0
- data/docs/de-DE/entity-dsl.md +53 -0
- data/docs/de-DE/native-runtime-quality-report.md +74 -0
- data/docs/de-DE/oql-sql.md +211 -0
- data/docs/de-DE/platform-api-integration.md +36 -0
- data/docs/de-DE/platform-operations.md +243 -0
- data/docs/de-DE/project-structure.md +32 -0
- data/docs/de-DE/ruby-first-roadmap.md +116 -0
- data/docs/de-DE/scaffolds.md +59 -0
- data/docs/de-DE/semantic-refactoring.md +56 -0
- data/docs/de-DE/team-server.md +33 -0
- data/docs/de-DE/validation-matrix.md +64 -0
- data/docs/de-DE/vetclinic-acceptance.md +24 -0
- data/docs/de-DE/writing.md +138 -0
- data/docs/en-US/README.md +24 -0
- data/docs/en-US/architectural-patterns.md +151 -0
- data/docs/en-US/architectural-standard.md +89 -0
- data/docs/en-US/architecture.md +170 -0
- data/docs/en-US/compiler.md +157 -0
- data/docs/en-US/conventions.md +82 -0
- data/docs/en-US/design-system.md +163 -0
- data/docs/en-US/entity-dsl.md +53 -0
- data/docs/en-US/native-runtime-quality-report.md +73 -0
- data/docs/en-US/oql-sql.md +201 -0
- data/docs/en-US/platform-api-integration.md +36 -0
- data/docs/en-US/platform-operations.md +244 -0
- data/docs/en-US/project-structure.md +49 -0
- data/docs/en-US/ruby-first-roadmap.md +186 -0
- data/docs/en-US/scaffolds.md +57 -0
- data/docs/en-US/semantic-refactoring.md +198 -0
- data/docs/en-US/team-server.md +32 -0
- data/docs/en-US/validation-matrix.md +257 -0
- data/docs/en-US/vetclinic-acceptance.md +23 -0
- data/docs/en-US/writing.md +655 -0
- data/docs/pt-BR/README.md +24 -0
- data/docs/pt-BR/architectural-patterns.md +25 -0
- data/docs/pt-BR/architectural-standard.md +533 -0
- data/docs/pt-BR/architecture.md +300 -0
- data/docs/pt-BR/compiler.md +162 -0
- data/docs/pt-BR/conventions.md +19 -0
- data/docs/pt-BR/design-system.md +65 -0
- data/docs/pt-BR/entity-dsl.md +116 -0
- data/docs/pt-BR/native-runtime-quality-report.md +73 -0
- data/docs/pt-BR/oql-sql.md +208 -0
- data/docs/pt-BR/platform-api-integration.md +36 -0
- data/docs/pt-BR/platform-operations.md +260 -0
- data/docs/pt-BR/project-structure.md +32 -0
- data/docs/pt-BR/ruby-first-roadmap.md +239 -0
- data/docs/pt-BR/scaffolds.md +104 -0
- data/docs/pt-BR/semantic-refactoring.md +55 -0
- data/docs/pt-BR/team-server.md +59 -0
- data/docs/pt-BR/validation-matrix.md +86 -0
- data/docs/pt-BR/vetclinic-acceptance.md +50 -0
- data/docs/pt-BR/writing.md +177 -0
- data/examples/sudoku_evaluation.rb +14 -0
- data/examples/sudoku_functional_test.rb +28 -0
- data/lib/mxrb/architecture/graph.rb +124 -0
- data/lib/mxrb/architecture/validator.rb +98 -0
- data/lib/mxrb/benchmark.rb +33 -0
- data/lib/mxrb/compare.rb +390 -0
- data/lib/mxrb/compiler/adapter.rb +96 -0
- data/lib/mxrb/compiler/artifact_document_compiler.rb +102 -0
- data/lib/mxrb/compiler/artifact_materializer.rb +53 -0
- data/lib/mxrb/compiler/client_model_materializer.rb +42 -0
- data/lib/mxrb/compiler/code_action_document_compiler.rb +64 -0
- data/lib/mxrb/compiler/code_action_materializer.rb +35 -0
- data/lib/mxrb/compiler/code_action_type_compiler.rb +32 -0
- data/lib/mxrb/compiler/combo_box_bundle_compiler.rb +314 -0
- data/lib/mxrb/compiler/compatibility_analyzer.rb +169 -0
- data/lib/mxrb/compiler/constants_materializer.rb +111 -0
- data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +290 -0
- data/lib/mxrb/compiler/database_connector_action_compiler.rb +280 -0
- data/lib/mxrb/compiler/deployment_asset_copier.rb +60 -0
- data/lib/mxrb/compiler/deployment_bootstrapper.rb +208 -0
- data/lib/mxrb/compiler/deployment_materializer.rb +43 -0
- data/lib/mxrb/compiler/deployment_metadata.rb +64 -0
- data/lib/mxrb/compiler/domain_document_compiler.rb +153 -0
- data/lib/mxrb/compiler/domain_model_materializer.rb +35 -0
- data/lib/mxrb/compiler/domain_security_compiler.rb +82 -0
- data/lib/mxrb/compiler/gallery_bundle_compiler.rb +190 -0
- data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +281 -0
- data/lib/mxrb/compiler/image_bundle_compiler.rb +216 -0
- data/lib/mxrb/compiler/java_proxy_generator.rb +509 -0
- data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +267 -0
- data/lib/mxrb/compiler/legacy_page_builder.rb +287 -0
- data/lib/mxrb/compiler/mda.rb +99 -0
- data/lib/mxrb/compiler/microflow_document_compiler.rb +85 -0
- data/lib/mxrb/compiler/microflow_materializer.rb +48 -0
- data/lib/mxrb/compiler/microflow_node_compiler.rb +250 -0
- data/lib/mxrb/compiler/model_package.rb +116 -0
- data/lib/mxrb/compiler/model_values.rb +67 -0
- data/lib/mxrb/compiler/nanoflow_program_compiler.rb +483 -0
- data/lib/mxrb/compiler/navigation_document_compiler.rb +93 -0
- data/lib/mxrb/compiler/packager.rb +100 -0
- data/lib/mxrb/compiler/page_bundle_builder.rb +56 -0
- data/lib/mxrb/compiler/page_bundle_compiler.rb +2004 -0
- data/lib/mxrb/compiler/page_document_compiler.rb +77 -0
- data/lib/mxrb/compiler/portable_packager.rb +356 -0
- data/lib/mxrb/compiler/project_jar_archive.rb +84 -0
- data/lib/mxrb/compiler/project_jar_builder.rb +121 -0
- data/lib/mxrb/compiler/project_materializer.rb +139 -0
- data/lib/mxrb/compiler/project_model_orderer.rb +52 -0
- data/lib/mxrb/compiler/runtime_data_types.rb +31 -0
- data/lib/mxrb/compiler/runtime_model_schema.rb +90 -0
- data/lib/mxrb/compiler/schemas/runtime-10.24.0.73019.json +1052 -0
- data/lib/mxrb/compiler/schemas/runtime-11.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-6.10.8.json +1148 -0
- data/lib/mxrb/compiler/schemas/runtime-7.17.0.json +1144 -0
- data/lib/mxrb/compiler/schemas/runtime-7.5.0.json +1215 -0
- data/lib/mxrb/compiler/schemas/runtime-9.6.1.29396.json +868 -0
- data/lib/mxrb/compiler/schemas/system-model-10.24.0.73019.b64 +1135 -0
- data/lib/mxrb/compiler/schemas/system-model-11.12.1.b64 +815 -0
- data/lib/mxrb/compiler/schemas/system-model-6.10.8.b64 +497 -0
- data/lib/mxrb/compiler/schemas/system-model-7.17.0.b64 +519 -0
- data/lib/mxrb/compiler/schemas/system-model-7.5.0.b64 +504 -0
- data/lib/mxrb/compiler/schemas/system-model-9.6.1.29396.b64 +816 -0
- data/lib/mxrb/compiler/security_materializer.rb +104 -0
- data/lib/mxrb/compiler/settings_document_compiler.rb +45 -0
- data/lib/mxrb/compiler/settings_materializer.rb +25 -0
- data/lib/mxrb/compiler/source_model.rb +128 -0
- data/lib/mxrb/compiler/system_model_seed.rb +88 -0
- data/lib/mxrb/compiler/system_queue_materializer.rb +54 -0
- data/lib/mxrb/compiler/system_text_materializer.rb +49 -0
- data/lib/mxrb/compiler/translation_materializer.rb +95 -0
- data/lib/mxrb/compiler/web_bundle_builder.rb +155 -0
- data/lib/mxrb/compiler/web_list_data_source.rb +139 -0
- data/lib/mxrb/compiler/web_operation_compiler.rb +614 -0
- data/lib/mxrb/compiler/web_shell_materializer.rb +194 -0
- data/lib/mxrb/compiler/widget_package_extractor.rb +63 -0
- data/lib/mxrb/doctor.rb +116 -0
- data/lib/mxrb/dsl/builder.rb +1636 -0
- data/lib/mxrb/errors.rb +18 -0
- data/lib/mxrb/evaluation.rb +131 -0
- data/lib/mxrb/exporter.rb +1716 -0
- data/lib/mxrb/frontend/migrator.rb +775 -0
- data/lib/mxrb/functional.rb +307 -0
- data/lib/mxrb/github/annotator.rb +161 -0
- data/lib/mxrb/initializer.rb +257 -0
- data/lib/mxrb/integrity/validator.rb +136 -0
- data/lib/mxrb/io/bson_codec.rb +145 -0
- data/lib/mxrb/io/mpr_file.rb +558 -0
- data/lib/mxrb/io/mxunit_codec.rb +42 -0
- data/lib/mxrb/marketplace.rb +380 -0
- data/lib/mxrb/model/association.rb +98 -0
- data/lib/mxrb/model/attribute.rb +100 -0
- data/lib/mxrb/model/connector.rb +17 -0
- data/lib/mxrb/model/design_materializer.rb +133 -0
- data/lib/mxrb/model/design_migration.rb +126 -0
- data/lib/mxrb/model/design_system.rb +111 -0
- data/lib/mxrb/model/domain_model.rb +51 -0
- data/lib/mxrb/model/entity.rb +180 -0
- data/lib/mxrb/model/menu.rb +38 -0
- data/lib/mxrb/model/microflow.rb +100 -0
- data/lib/mxrb/model/module.rb +142 -0
- data/lib/mxrb/model/navigation.rb +127 -0
- data/lib/mxrb/model/page.rb +410 -0
- data/lib/mxrb/model/project.rb +187 -0
- data/lib/mxrb/model/unit.rb +48 -0
- data/lib/mxrb/module_initializer.rb +75 -0
- data/lib/mxrb/official_marketplace/content_api.rb +292 -0
- data/lib/mxrb/official_marketplace/dependency_resolver.rb +329 -0
- data/lib/mxrb/official_marketplace/lifecycle.rb +367 -0
- data/lib/mxrb/official_marketplace/module_package_importer.rb +318 -0
- data/lib/mxrb/official_marketplace/widget_package_installer.rb +414 -0
- data/lib/mxrb/official_marketplace.rb +720 -0
- data/lib/mxrb/oql/analyzer.rb +155 -0
- data/lib/mxrb/oql/index_advisor.rb +85 -0
- data/lib/mxrb/oql/plan_analyzer.rb +185 -0
- data/lib/mxrb/oql/server.rb +132 -0
- data/lib/mxrb/oql/sql_server_plan_analyzer.rb +190 -0
- data/lib/mxrb/oql/sql_server_workload_analyzer.rb +80 -0
- data/lib/mxrb/oql/workload_analyzer.rb +175 -0
- data/lib/mxrb/oql/workload_baseline.rb +60 -0
- data/lib/mxrb/oql.rb +424 -0
- data/lib/mxrb/project_lifecycle.rb +76 -0
- data/lib/mxrb/protocols/plan.rb +86 -0
- data/lib/mxrb/protocols.rb +123 -0
- data/lib/mxrb/runtime/database_workspace.rb +677 -0
- data/lib/mxrb/runtime/docker_executor.rb +41 -0
- data/lib/mxrb/runtime/docker_workspace.rb +29 -0
- data/lib/mxrb/runtime/executor.rb +169 -0
- data/lib/mxrb/runtime/java_locator.rb +26 -0
- data/lib/mxrb/runtime/native.rb +474 -0
- data/lib/mxrb/runtime/sql_server_database.rb +146 -0
- data/lib/mxrb/runtime/toolchain.rb +82 -0
- data/lib/mxrb/scaffold/cli.rb +113 -0
- data/lib/mxrb/scaffold/generator.rb +246 -0
- data/lib/mxrb/scaffold/help.rb +80 -0
- data/lib/mxrb/scaffold/page_templates.rb +54 -0
- data/lib/mxrb/scaffold/recipes.rb +198 -0
- data/lib/mxrb/scaffold/registry.rb +82 -0
- data/lib/mxrb/scaffold/templates.rb +723 -0
- data/lib/mxrb/scaffold/transaction.rb +84 -0
- data/lib/mxrb/schema/tables.rb +55 -0
- data/lib/mxrb/semantic/analyzer.rb +473 -0
- data/lib/mxrb/semantic/batch_plan.rb +68 -0
- data/lib/mxrb/semantic/domain_mutator.rb +419 -0
- data/lib/mxrb/semantic/embedder.rb +51 -0
- data/lib/mxrb/semantic/extractor.rb +290 -0
- data/lib/mxrb/semantic/index.rb +623 -0
- data/lib/mxrb/semantic/inliner.rb +216 -0
- data/lib/mxrb/semantic/mover.rb +153 -0
- data/lib/mxrb/semantic/onnx_embedder.rb +34 -0
- data/lib/mxrb/semantic/remover.rb +70 -0
- data/lib/mxrb/semantic/renamer.rb +211 -0
- data/lib/mxrb/semantic/tfidf_embedder.rb +46 -0
- data/lib/mxrb/semantic/vec_store.rb +75 -0
- data/lib/mxrb/team_server.rb +390 -0
- data/lib/mxrb/templates/project/10.24.0.73019.json +32 -0
- data/lib/mxrb/templates/project/11.12.1.json +32 -0
- data/lib/mxrb/templates/project/6.10.8.json +50 -0
- data/lib/mxrb/templates/project/7.17.0.json +50 -0
- data/lib/mxrb/templates/project/7.5.0.json +50 -0
- data/lib/mxrb/templates/project/9.6.1.29396.json +50 -0
- data/lib/mxrb/version.rb +5 -0
- data/lib/mxrb/widget_package.rb +338 -0
- data/lib/mxrb/widget_synchronizer.rb +31 -0
- data/lib/mxrb/writer.rb +3775 -0
- data/lib/mxrb.rb +188 -0
- data/marketplace/catalog.json +10 -0
- data/marketplace/modules/shared-kernel/domain/README.md +4 -0
- data/marketplace/modules/shared-kernel/module.rb +5 -0
- data/marketplace/modules/shared-kernel/mxrb-module.json +9 -0
- metadata +447 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Compiler
|
|
7
|
+
# Compiles the official React Gallery widget with XPath, microflow, or nanoflow data sources.
|
|
8
|
+
class GalleryBundleCompiler # rubocop:disable Metrics/ClassLength
|
|
9
|
+
include ModelValues
|
|
10
|
+
|
|
11
|
+
WIDGET_ID = 'com.mendix.widget.web.gallery.Gallery'
|
|
12
|
+
|
|
13
|
+
attr_reader :data_source, :entity_name
|
|
14
|
+
|
|
15
|
+
def initialize(source, page_name, widget)
|
|
16
|
+
@source = source
|
|
17
|
+
@page_name = page_name
|
|
18
|
+
@widget = widget
|
|
19
|
+
@index = document_index
|
|
20
|
+
@values = property_values(widget['Object'])
|
|
21
|
+
@data_source = WebListDataSource.new(source, widget)
|
|
22
|
+
@entity_name = data_source.entity
|
|
23
|
+
@xpath_arguments = resolve_xpath_arguments
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def supported?
|
|
27
|
+
widget_type&.fetch('WidgetId', nil) == WIDGET_ID &&
|
|
28
|
+
@data_source.supported? && !entity_name.to_s.empty? && !@xpath_arguments.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def content_widgets = array(@values['content']&.last&.fetch('Widgets', nil))
|
|
32
|
+
def widget_key = "p.#{@page_name}.#{@widget['Name']}"
|
|
33
|
+
def data_source_id = "p.#{Digest::SHA256.hexdigest(widget_key)[0, 6].to_i(16)}"
|
|
34
|
+
|
|
35
|
+
def render(content, nanoflow_reference: nil)
|
|
36
|
+
@nanoflow_reference = nanoflow_reference
|
|
37
|
+
values = primitives(@values).merge(
|
|
38
|
+
key: widget_key, '$widgetId': widget_key, datasource: raw(datasource),
|
|
39
|
+
content: raw("TemplatedWidgetProperty({ children: () => #{content}, " \
|
|
40
|
+
"dataSourceId: #{JSON.generate(data_source_id)}, editable: false })"),
|
|
41
|
+
itemSelection: raw(selection), class: css_class
|
|
42
|
+
)
|
|
43
|
+
"React.createElement($Gallery, #{js_object(values)})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def datasource
|
|
49
|
+
config = {
|
|
50
|
+
dataSourceId: data_source_id,
|
|
51
|
+
operationId: WebOperationCompiler.operation_id(@page_name, @widget['Name'])
|
|
52
|
+
}
|
|
53
|
+
return xpath_datasource(config) if @data_source.xpath?
|
|
54
|
+
return microflow_datasource(config) if @data_source.microflow?
|
|
55
|
+
|
|
56
|
+
nanoflow_datasource(config)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def xpath_datasource(config)
|
|
60
|
+
values = config.merge(entity: entity_name, sort: [])
|
|
61
|
+
unless @xpath_arguments.empty?
|
|
62
|
+
values[:arguments] = @xpath_arguments
|
|
63
|
+
values[:fetchOnlyWithAllParams] = true
|
|
64
|
+
end
|
|
65
|
+
"DatabaseObjectListProperty(#{js_object(values)})"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def resolve_xpath_arguments
|
|
69
|
+
return {} unless @data_source.xpath?
|
|
70
|
+
|
|
71
|
+
names = xpath_variables(@data_source.xpath_constraint)
|
|
72
|
+
return {} if names.empty?
|
|
73
|
+
|
|
74
|
+
parameters = object_page_parameters(page_document, names)
|
|
75
|
+
return unless parameters
|
|
76
|
+
|
|
77
|
+
parameters.to_h { |name, _entity| [name, ["$#{name}", :undefined, false]] }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def page_document
|
|
81
|
+
module_name, document_name = @page_name.split('.', 2)
|
|
82
|
+
@source.units_of('Forms$Page').find do |unit|
|
|
83
|
+
unit.module_name == module_name && unit.document['Name'] == document_name
|
|
84
|
+
end&.document
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def microflow_datasource(config)
|
|
88
|
+
values = config.merge(argMap: {}, fetchOnlyWithAllParams: false)
|
|
89
|
+
"MicroflowObjectListProperty(#{js_object(values)})"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def nanoflow_datasource(config)
|
|
93
|
+
values = config.except(:operationId).merge(
|
|
94
|
+
source: { nanoflow: raw(@nanoflow_reference || 'undefined') },
|
|
95
|
+
argMap: {}, fetchOnlyWithAllParams: false
|
|
96
|
+
)
|
|
97
|
+
"NanoflowObjectListProperty(#{js_object(values)})"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def selection
|
|
101
|
+
type = @values['itemSelection']&.last&.fetch('Selection', 'None') || 'None'
|
|
102
|
+
"SelectionProperty(#{js_object(selectionType: type, dataSourceId: data_source_id)})"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def primitives(values)
|
|
106
|
+
values.each_with_object({}) do |(key, pair), result|
|
|
107
|
+
type, value = pair
|
|
108
|
+
compiled = primitive(type, value)
|
|
109
|
+
result[key.to_sym] = compiled unless compiled.equal?(:undefined)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def primitive(type, value) # rubocop:disable Metrics/CyclomaticComplexity
|
|
114
|
+
compiled = case type
|
|
115
|
+
when 'Boolean' then value['PrimitiveValue'] == 'true'
|
|
116
|
+
when 'Integer' then value['PrimitiveValue'].to_i
|
|
117
|
+
when 'Enumeration' then value['PrimitiveValue'].to_s
|
|
118
|
+
when 'TextTemplate' then raw(expression(translated_text(value['TextTemplate'])))
|
|
119
|
+
when 'Widgets' then [] if array(value['Widgets']).empty?
|
|
120
|
+
end
|
|
121
|
+
compiled.nil? ? :undefined : compiled
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def expression(value)
|
|
125
|
+
"ExpressionProperty(#{js_object(expression: { expr: { type: 'literal', value: }, args: {} })})"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def raw(value) = { '$raw' => value }
|
|
129
|
+
|
|
130
|
+
def js_object(value)
|
|
131
|
+
pairs = value.map { |key, item| "#{JSON.generate(key)}: #{js_value(item)}" }
|
|
132
|
+
"{ #{pairs.join(', ')} }"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def js_value(value)
|
|
136
|
+
return value['$raw'] if value.is_a?(Hash) && value.key?('$raw')
|
|
137
|
+
return 'undefined' if value == :undefined
|
|
138
|
+
return "[#{value.map { js_value(_1) }.join(', ')}]" if value.is_a?(Array)
|
|
139
|
+
return js_object(value) if value.is_a?(Hash)
|
|
140
|
+
|
|
141
|
+
JSON.generate(value)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def property_values(object)
|
|
145
|
+
array(object&.fetch('Properties', nil)).filter_map do |property|
|
|
146
|
+
type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
|
|
147
|
+
next unless type
|
|
148
|
+
|
|
149
|
+
[type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
|
|
150
|
+
end.to_h
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def css_class
|
|
154
|
+
["mx-name-#{@widget['Name']}", @widget.dig('Appearance', 'Class')]
|
|
155
|
+
.map(&:to_s).reject(&:empty?).uniq.join(' ')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def translated_text(text)
|
|
159
|
+
items = array(text&.dig('Template', 'Items'))
|
|
160
|
+
items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
|
|
161
|
+
items.first&.fetch('Text', '') || ''
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def widget_type
|
|
165
|
+
object_type_id = IO::BsonCodec.extract_id(@widget.dig('Object', 'TypePointer'))
|
|
166
|
+
return unless object_type_id
|
|
167
|
+
|
|
168
|
+
@index.values.find do |document|
|
|
169
|
+
IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == object_type_id
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def document_index
|
|
174
|
+
return @source.document_index if @source.is_a?(SourceModel)
|
|
175
|
+
|
|
176
|
+
{}.tap { |index| @source.documents.each { index_document(_1, index) } }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def index_document(value, index)
|
|
180
|
+
case value
|
|
181
|
+
when Hash
|
|
182
|
+
id = IO::BsonCodec.extract_id(value['$ID'])
|
|
183
|
+
index[id] = value if id
|
|
184
|
+
value.each_value { index_document(_1, index) }
|
|
185
|
+
when Array then value.each { index_document(_1, index) }
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end # rubocop:enable Metrics/ClassLength
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'zip'
|
|
5
|
+
|
|
6
|
+
module Mxrb
|
|
7
|
+
module Compiler
|
|
8
|
+
# Compiles schema-described pluggable widgets whose properties use the standard client contracts.
|
|
9
|
+
# rubocop:disable Metrics
|
|
10
|
+
class GenericWidgetBundleCompiler
|
|
11
|
+
include ModelValues
|
|
12
|
+
|
|
13
|
+
attr_reader :component_name, :module_path
|
|
14
|
+
|
|
15
|
+
def initialize(source, page_name, widget, scope:, entity:, render_widgets: nil, key_prefix: 'p',
|
|
16
|
+
action_property: nil)
|
|
17
|
+
@source = source
|
|
18
|
+
@page_name = page_name
|
|
19
|
+
@widget = widget
|
|
20
|
+
@scope = scope
|
|
21
|
+
@entity = entity.to_s
|
|
22
|
+
@render_widgets = render_widgets
|
|
23
|
+
@key_prefix = key_prefix
|
|
24
|
+
@action_property = action_property
|
|
25
|
+
@index = source.document_index
|
|
26
|
+
@widget_type = find_widget_type
|
|
27
|
+
@values = property_values(widget['Object'])
|
|
28
|
+
@list_data_source = @values.values.find { _1.first == 'DataSource' }&.last
|
|
29
|
+
@component_name = widget_id.to_s.split('.').last
|
|
30
|
+
@module_path = widget_id.to_s.tr('.', '/')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def supported?
|
|
34
|
+
present_identifier?(@component_name) && package_module? &&
|
|
35
|
+
@values.values.all? { supported_property?(_1) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def render
|
|
39
|
+
properties = @values.each_with_object({}) do |(key, pair), result|
|
|
40
|
+
compiled = compile_property(pair)
|
|
41
|
+
result[key.to_sym] = compiled unless compiled.equal?(:undefined)
|
|
42
|
+
end
|
|
43
|
+
properties.merge!(key: widget_key, '$widgetId': widget_key, class: css_class)
|
|
44
|
+
"React.createElement($#{@component_name}, #{javascript(properties)})"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def widget_id = @widget_type&.fetch('WidgetId', nil)
|
|
50
|
+
|
|
51
|
+
def find_widget_type
|
|
52
|
+
object_type_id = IO::BsonCodec.extract_id(@widget.dig('Object', 'TypePointer'))
|
|
53
|
+
@index.values.find do |document|
|
|
54
|
+
IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == object_type_id
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def property_values(object)
|
|
59
|
+
array(object&.fetch('Properties', nil)).filter_map do |property|
|
|
60
|
+
type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
|
|
61
|
+
next unless type
|
|
62
|
+
|
|
63
|
+
[type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
|
|
64
|
+
end.to_h
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def supported_property?(pair)
|
|
68
|
+
type, value = pair
|
|
69
|
+
return true if %w[Boolean Integer Decimal Enumeration String System].include?(type)
|
|
70
|
+
return value['DataSource'].nil? || !database_list_property(value).nil? if type == 'DataSource'
|
|
71
|
+
return true if type == 'Association'
|
|
72
|
+
return true if type == 'Attribute' && value['AttributeRef'].nil?
|
|
73
|
+
return true if %w[Expression TextTemplate].include?(type) && expression_property(type, value)
|
|
74
|
+
return true if type == 'Attribute' && attribute_property(value)
|
|
75
|
+
return no_action?(value) || !compiled_action(value).nil? if type == 'Action'
|
|
76
|
+
return array(value['Widgets']).empty? || @render_widgets if type == 'Widgets'
|
|
77
|
+
return array(value['Objects']).empty? if type == 'Object'
|
|
78
|
+
|
|
79
|
+
false
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def compile_property(pair)
|
|
83
|
+
type, value = pair
|
|
84
|
+
case type
|
|
85
|
+
when 'Boolean' then value['PrimitiveValue'] == 'true'
|
|
86
|
+
when 'Integer' then value['PrimitiveValue'].to_i
|
|
87
|
+
when 'Decimal' then value['PrimitiveValue'].to_f
|
|
88
|
+
when 'Enumeration', 'String' then value['PrimitiveValue'].to_s
|
|
89
|
+
when 'Expression', 'TextTemplate' then raw(expression_property(type, value))
|
|
90
|
+
when 'DataSource' then database_list_property(value)&.then { raw(_1) } || :undefined
|
|
91
|
+
when 'Action' then compiled_action(value)&.then { raw(_1) } || :undefined
|
|
92
|
+
when 'Association' then :undefined
|
|
93
|
+
when 'Attribute' then attribute_property(value)&.then { raw(_1) } || :undefined
|
|
94
|
+
when 'Widgets' then compile_widgets(value)
|
|
95
|
+
when 'Object' then []
|
|
96
|
+
else :undefined
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def compile_widgets(value)
|
|
101
|
+
widgets = array(value['Widgets'])
|
|
102
|
+
return [] if widgets.empty?
|
|
103
|
+
|
|
104
|
+
@render_widgets.call(widgets)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def expression_property(type, value)
|
|
108
|
+
expression = type == 'Expression' ? compile_expression(value['Expression']) : compile_template(value)
|
|
109
|
+
return unless expression
|
|
110
|
+
|
|
111
|
+
args = expression_variables(expression).to_h do |variable|
|
|
112
|
+
[variable.to_sym, { widget: @scope, source: 'object' }]
|
|
113
|
+
end
|
|
114
|
+
property = @list_data_source ? 'ListExpressionProperty' : 'ExpressionProperty'
|
|
115
|
+
config = { expression: { expr: expression, args: } }
|
|
116
|
+
config[:dataSourceId] = widget_key if @list_data_source
|
|
117
|
+
"#{property}(#{javascript(config)})"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def database_list_property(value)
|
|
121
|
+
source = value['DataSource'] || {}
|
|
122
|
+
return unless source['$Type'] == 'CustomWidgets$CustomWidgetXPathSource'
|
|
123
|
+
|
|
124
|
+
entity = source.dig('EntityRef', 'Entity').to_s
|
|
125
|
+
return unless present_qualified_name?(entity)
|
|
126
|
+
|
|
127
|
+
config = {
|
|
128
|
+
dataSourceId: widget_key, entity:,
|
|
129
|
+
operationId: WebOperationCompiler.operation_id(@page_name, @widget['Name']),
|
|
130
|
+
sort: sort_items(source)
|
|
131
|
+
}
|
|
132
|
+
"DatabaseObjectListProperty(#{javascript(config)})"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def compiled_action(value)
|
|
136
|
+
action = value['Action'] || {}
|
|
137
|
+
return if action['$Type'].to_s.empty? || action['$Type'] == 'Forms$NoAction'
|
|
138
|
+
|
|
139
|
+
@action_property&.call(action)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def sort_items(source)
|
|
143
|
+
array(source.dig('SortBar', 'SortItems')).filter_map do |item|
|
|
144
|
+
attribute = item.dig('AttributeRef', 'Attribute').to_s.split('.').last
|
|
145
|
+
next unless present_identifier?(attribute)
|
|
146
|
+
|
|
147
|
+
[attribute, item['SortOrder'].to_s.casecmp('descending').zero? ? 'desc' : 'asc']
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def compile_expression(source)
|
|
152
|
+
value = source.to_s.strip
|
|
153
|
+
return { type: 'literal', value: nil } if value.empty? || value == 'empty'
|
|
154
|
+
return variable_expression(value) if value.match?(%r{\A\$[A-Za-z_]\w*(?:/[A-Za-z_]\w*)*\z})
|
|
155
|
+
return { type: 'literalNumeric', value: } if value.match?(/\A-?\d+(?:\.\d+)?\z/)
|
|
156
|
+
return { type: 'literal', value: value == 'true' } if %w[true false].include?(value)
|
|
157
|
+
return { type: 'literal', value: value[1..-2] } if quoted?(value)
|
|
158
|
+
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def variable_expression(source)
|
|
163
|
+
_variable, *path = source.delete_prefix('$').split('/')
|
|
164
|
+
{ type: 'variable', variable: 'currentObject' }.tap do |result|
|
|
165
|
+
result[:path] = path.join('/') unless path.empty?
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def compile_template(value)
|
|
170
|
+
template = value['TextTemplate']
|
|
171
|
+
text = translated_text(template)
|
|
172
|
+
parameters = array(template&.fetch('Parameters', nil))
|
|
173
|
+
return { type: 'literal', value: text } if parameters.empty?
|
|
174
|
+
|
|
175
|
+
expressions = parameters.map { template_parameter(_1) }
|
|
176
|
+
return unless expressions.all?
|
|
177
|
+
return expressions.first if text == '{1}' && expressions.one?
|
|
178
|
+
|
|
179
|
+
format_expression(text, expressions)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def template_parameter(parameter)
|
|
183
|
+
attribute = parameter.dig('AttributeRef', 'Attribute').to_s
|
|
184
|
+
return compile_expression(parameter['Expression']) if attribute.empty?
|
|
185
|
+
|
|
186
|
+
name = attribute.split('.').last
|
|
187
|
+
variable = { type: 'variable', variable: 'currentObject', path: name }
|
|
188
|
+
enumeration = enumeration_name(attribute)
|
|
189
|
+
return variable unless enumeration
|
|
190
|
+
|
|
191
|
+
{ type: 'function', name: 'getCaption',
|
|
192
|
+
parameters: [variable, { type: 'literal', value: enumeration }] }
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def format_expression(text, expressions)
|
|
196
|
+
pieces = text.split(/(\{\d+\})/).filter_map do |piece|
|
|
197
|
+
index = piece[/\A\{(\d+)\}\z/, 1]
|
|
198
|
+
index ? expressions[index.to_i - 1] : ({ type: 'literal', value: piece } unless piece.empty?)
|
|
199
|
+
end
|
|
200
|
+
pieces.compact.reduce { |left, right| { type: 'function', name: '+', parameters: [left, right] } }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def attribute_property(value)
|
|
204
|
+
attribute = value.dig('AttributeRef', 'Attribute').to_s
|
|
205
|
+
return unless @scope && attribute.include?('.')
|
|
206
|
+
|
|
207
|
+
entity, _, name = attribute.rpartition('.')
|
|
208
|
+
"AttributeProperty(#{javascript(
|
|
209
|
+
scope: @scope, path: '', entity:, attribute: name,
|
|
210
|
+
onChange: { type: 'doNothing', argMap: {}, config: {}, disabledDuringExecution: true },
|
|
211
|
+
isList: false, validation: nil, formatting: {}
|
|
212
|
+
)})"
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def expression_variables(expression)
|
|
216
|
+
case expression
|
|
217
|
+
when Hash
|
|
218
|
+
own = expression[:type] == 'variable' ? [expression[:variable]] : []
|
|
219
|
+
own + expression.values.flat_map { expression_variables(_1) }
|
|
220
|
+
when Array then expression.flat_map { expression_variables(_1) }
|
|
221
|
+
else []
|
|
222
|
+
end.uniq
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def enumeration_name(attribute)
|
|
226
|
+
module_name, entity_name, attribute_name = attribute.split('.', 3)
|
|
227
|
+
domain = @source.units_of('DomainModels$DomainModel').find { _1.module_name == module_name }
|
|
228
|
+
entity = array(domain&.document&.fetch('Entities', nil)).find { _1['Name'] == entity_name }
|
|
229
|
+
member = array(entity&.fetch('Attributes', nil)).find { _1['Name'] == attribute_name }
|
|
230
|
+
member&.dig('NewType', 'Enumeration').to_s.then { _1.empty? ? nil : _1 }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def translated_text(template)
|
|
234
|
+
items = array(template&.dig('Template', 'Items'))
|
|
235
|
+
items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
|
|
236
|
+
items.first&.fetch('Text', '') || ''
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def package_module?
|
|
240
|
+
root = File.dirname(@source.path)
|
|
241
|
+
direct = File.join(root, 'widgets', "#{@module_path}.mjs")
|
|
242
|
+
return true if File.file?(direct)
|
|
243
|
+
|
|
244
|
+
entry = "#{@module_path}.mjs"
|
|
245
|
+
Dir.glob(File.join(root, 'widgets', '*.mpk')).any? do |path|
|
|
246
|
+
Zip::File.open(path) { _1.find_entry(entry) }
|
|
247
|
+
rescue Zip::Error
|
|
248
|
+
false
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def no_action?(value) = value['Action'].nil? || value.dig('Action', '$Type') == 'Forms$NoAction'
|
|
253
|
+
def widget_key = "#{@key_prefix}.#{@page_name}.#{@widget['Name']}"
|
|
254
|
+
|
|
255
|
+
def css_class
|
|
256
|
+
["mx-name-#{@widget['Name']}", @widget.dig('Appearance', 'Class')]
|
|
257
|
+
.map(&:to_s).reject(&:empty?).uniq.join(' ')
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def present_identifier?(value) = value.to_s.match?(/\A[A-Za-z_$][A-Za-z0-9_$]*\z/)
|
|
261
|
+
|
|
262
|
+
def present_qualified_name?(value)
|
|
263
|
+
value.to_s.split('.').length >= 2 && value.to_s.split('.').all? { present_identifier?(_1) }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def quoted?(value) = value.start_with?("'", '"') && value.end_with?(value[0])
|
|
267
|
+
def raw(value) = { '$raw' => value }
|
|
268
|
+
|
|
269
|
+
def javascript(value)
|
|
270
|
+
return value['$raw'] if value.is_a?(Hash) && value.key?('$raw')
|
|
271
|
+
return "[#{value.map { javascript(_1) }.join(', ')}]" if value.is_a?(Array)
|
|
272
|
+
if value.is_a?(Hash)
|
|
273
|
+
return "{ #{value.map { |key, item| "#{JSON.generate(key)}: #{javascript(item)}" }.join(', ')} }"
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
JSON.generate(value)
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
# rubocop:enable Metrics
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Mxrb
|
|
6
|
+
module Compiler
|
|
7
|
+
# Compiles the static-image subset of the official React Image widget.
|
|
8
|
+
# rubocop:disable Metrics
|
|
9
|
+
class ImageBundleCompiler
|
|
10
|
+
include ModelValues
|
|
11
|
+
|
|
12
|
+
WIDGET_ID = 'com.mendix.widget.web.image.Image'
|
|
13
|
+
|
|
14
|
+
def self.render_static(key, css_class, uri, options) # rubocop:disable Metrics/MethodLength
|
|
15
|
+
properties = {
|
|
16
|
+
key:, '$widgetId': key, datasource: 'image',
|
|
17
|
+
imageObject: raw("WebStaticImageProperty({ image: { uri: #{JSON.generate(uri)} } })"),
|
|
18
|
+
imageUrl: raw(expression('')), isBackgroundImage: false, onClickType: 'action',
|
|
19
|
+
alternativeText: raw(expression('')), widthUnit: unit(options[:width_unit]),
|
|
20
|
+
width: number(options[:width], 100), heightUnit: unit(options[:height_unit]),
|
|
21
|
+
height: number(options[:height], 100), iconSize: 14,
|
|
22
|
+
displayAs: 'fullImage', responsive: options[:responsive], minHeightUnit: 'none', minHeight: 0,
|
|
23
|
+
maxHeightUnit: 'none', maxHeight: 0, class: css_class
|
|
24
|
+
}
|
|
25
|
+
"React.createElement($Image, #{javascript(properties)})"
|
|
26
|
+
end # rubocop:enable Metrics/MethodLength
|
|
27
|
+
|
|
28
|
+
def self.expression(value)
|
|
29
|
+
"ExpressionProperty({ expression: { expr: { type: \"literal\", value: #{JSON.generate(value)} }, args: {} } })"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.raw(value) = { '$raw' => value }
|
|
33
|
+
def self.unit(value) = value.to_s.downcase.then { _1.empty? ? 'auto' : _1 }
|
|
34
|
+
def self.number(value, fallback) = Integer(value || fallback)
|
|
35
|
+
|
|
36
|
+
def self.javascript(value)
|
|
37
|
+
return value['$raw'] if value.is_a?(Hash) && value.key?('$raw')
|
|
38
|
+
return "[#{value.map { javascript(_1) }.join(', ')}]" if value.is_a?(Array)
|
|
39
|
+
if value.is_a?(Hash)
|
|
40
|
+
return "{ #{value.map { |key, item| "#{JSON.generate(key)}: #{javascript(item)}" }.join(', ')} }"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
JSON.generate(value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def initialize(source, page_name, widget, scope: nil, entity: nil, key_prefix: 'p', action_property: nil)
|
|
47
|
+
@source = source
|
|
48
|
+
@page_name = page_name
|
|
49
|
+
@widget = widget
|
|
50
|
+
@scope = scope
|
|
51
|
+
@entity = entity
|
|
52
|
+
@key_prefix = key_prefix
|
|
53
|
+
@action_property = action_property
|
|
54
|
+
@index = document_index
|
|
55
|
+
@values = property_values(widget['Object'])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def supported?
|
|
59
|
+
type = widget_type
|
|
60
|
+
type && type.fetch('WidgetId', nil) == WIDGET_ID && supported_source? && supported_action?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def render # rubocop:disable Metrics/AbcSize
|
|
64
|
+
values = primitive_properties.merge(
|
|
65
|
+
key: widget_key, '$widgetId': widget_key,
|
|
66
|
+
alternativeText: self.class.raw(self.class.expression(text_value('alternativeText'))),
|
|
67
|
+
class: css_class
|
|
68
|
+
)
|
|
69
|
+
if primitive('datasource') == 'image'
|
|
70
|
+
values[:imageObject] = self.class.raw(
|
|
71
|
+
"WebStaticImageProperty({ image: { uri: #{JSON.generate(image_uri)} } })"
|
|
72
|
+
)
|
|
73
|
+
values[:imageUrl] = self.class.raw(self.class.expression(text_value('imageUrl')))
|
|
74
|
+
else
|
|
75
|
+
values[:imageUrl] = self.class.raw(dynamic_image_url)
|
|
76
|
+
end
|
|
77
|
+
action = compiled_action
|
|
78
|
+
values[:onClick] = self.class.raw(action) if action
|
|
79
|
+
"React.createElement($Image, #{self.class.javascript(values)})"
|
|
80
|
+
end # rubocop:enable Metrics/AbcSize
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def supported_source?
|
|
85
|
+
return !image_uri.nil? if primitive('datasource') == 'image'
|
|
86
|
+
|
|
87
|
+
primitive('datasource') == 'imageUrl' && !dynamic_image_url.nil?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def supported_action?
|
|
91
|
+
pair = @values['onClick']
|
|
92
|
+
action = pair&.last&.fetch('Action', nil)
|
|
93
|
+
action.nil? || action['$Type'] == 'Forms$NoAction' || !compiled_action.nil?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def compiled_action
|
|
97
|
+
pair = @values['onClick']
|
|
98
|
+
action = pair&.last&.fetch('Action', nil)
|
|
99
|
+
return if action.nil? || action['$Type'] == 'Forms$NoAction'
|
|
100
|
+
|
|
101
|
+
@action_property&.call(action)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def dynamic_image_url
|
|
105
|
+
parameter = image_url_parameter
|
|
106
|
+
attribute = parameter&.dig('AttributeRef', 'Attribute').to_s
|
|
107
|
+
return unless @scope && attribute.include?('.')
|
|
108
|
+
|
|
109
|
+
path = array(parameter.dig('AttributeRef', 'EntityRef', 'Steps')).flat_map do |step|
|
|
110
|
+
[step['Association'], step['DestinationEntity']]
|
|
111
|
+
end.map(&:to_s).reject(&:empty?).join('/')
|
|
112
|
+
member = [path, attribute.split('.').last].reject(&:empty?).join('/')
|
|
113
|
+
config = {
|
|
114
|
+
expression: {
|
|
115
|
+
expr: { type: 'variable', variable: 'currentObject', path: member },
|
|
116
|
+
args: { currentObject: { widget: @scope, source: 'object' } }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
"ExpressionProperty(#{self.class.javascript(config)})"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def image_url_parameter
|
|
123
|
+
template = @values['imageUrl']&.last&.fetch('TextTemplate', nil)
|
|
124
|
+
parameters = array(template&.fetch('Parameters', nil))
|
|
125
|
+
parameters.one? ? parameters.first : nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def primitive_properties
|
|
129
|
+
@values.each_with_object({}) do |(key, (type, value)), result|
|
|
130
|
+
compiled = case type
|
|
131
|
+
when 'Boolean' then value['PrimitiveValue'] == 'true'
|
|
132
|
+
when 'Integer' then value['PrimitiveValue'].to_i
|
|
133
|
+
when 'Enumeration' then value['PrimitiveValue'].to_s
|
|
134
|
+
end
|
|
135
|
+
result[key.to_sym] = compiled unless compiled.nil?
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def primitive(key)
|
|
140
|
+
pair = @values[key]
|
|
141
|
+
return unless pair
|
|
142
|
+
|
|
143
|
+
pair.last.fetch('PrimitiveValue', nil)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def text_value(key)
|
|
147
|
+
pair = @values[key]
|
|
148
|
+
return translated_text(nil) unless pair
|
|
149
|
+
|
|
150
|
+
translated_text(pair.last.fetch('TextTemplate', nil))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def image_uri # rubocop:disable Metrics/AbcSize
|
|
154
|
+
pair = @values['imageObject']
|
|
155
|
+
reference = pair ? pair.last.fetch('Image', '').to_s : ''
|
|
156
|
+
module_name, collection_name, image_name = reference.split('.', 3)
|
|
157
|
+
unit = @source.units_of('Images$ImageCollection').find do |candidate|
|
|
158
|
+
candidate.module_name == module_name && candidate.document['Name'] == collection_name
|
|
159
|
+
end
|
|
160
|
+
return unless unit
|
|
161
|
+
|
|
162
|
+
image = array(unit.document['Images']).find { _1['Name'] == image_name }
|
|
163
|
+
return unless image
|
|
164
|
+
|
|
165
|
+
"img/#{[module_name, collection_name, image_name].join('$')}.#{image_format(image)}"
|
|
166
|
+
end # rubocop:enable Metrics/AbcSize
|
|
167
|
+
|
|
168
|
+
def translated_text(template)
|
|
169
|
+
items = template ? array(template.dig('Template', 'Items')) : []
|
|
170
|
+
items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
|
|
171
|
+
items.first&.fetch('Text', '') || ''
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def widget_key = "#{@key_prefix}.#{@page_name}.#{@widget['Name']}"
|
|
175
|
+
|
|
176
|
+
def css_class
|
|
177
|
+
["mx-name-#{@widget['Name']}", @widget.dig('Appearance', 'Class')]
|
|
178
|
+
.map(&:to_s).reject(&:empty?).uniq.join(' ')
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def property_values(object)
|
|
182
|
+
properties = object ? array(object.fetch('Properties', nil)) : []
|
|
183
|
+
properties.filter_map do |property|
|
|
184
|
+
type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
|
|
185
|
+
next unless type
|
|
186
|
+
|
|
187
|
+
[type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
|
|
188
|
+
end.to_h
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def widget_type
|
|
192
|
+
object_type_id = IO::BsonCodec.extract_id(@widget.dig('Object', 'TypePointer'))
|
|
193
|
+
@index.values.find do |document|
|
|
194
|
+
IO::BsonCodec.extract_id(document.dig('ObjectType', '$ID')) == object_type_id
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def document_index
|
|
199
|
+
return @source.document_index if @source.is_a?(SourceModel)
|
|
200
|
+
|
|
201
|
+
{}.tap { |index| @source.documents.each { index_document(_1, index) } }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def index_document(value, index)
|
|
205
|
+
case value
|
|
206
|
+
when Hash
|
|
207
|
+
id = IO::BsonCodec.extract_id(value['$ID'])
|
|
208
|
+
index[id] = value if id
|
|
209
|
+
value.each_value { index_document(_1, index) }
|
|
210
|
+
when Array then value.each { index_document(_1, index) }
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
# rubocop:enable Metrics
|
|
215
|
+
end
|
|
216
|
+
end
|