mxrb 0.1.2 → 0.1.4

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 (191) hide show
  1. checksums.yaml +4 -4
  2. data/bin/mxrb +458 -10
  3. data/docs/de-DE/README.md +1 -0
  4. data/docs/de-DE/oql-sql.md +42 -0
  5. data/docs/de-DE/project-structure.md +201 -2
  6. data/docs/de-DE/runtime-ruby.md +104 -0
  7. data/docs/de-DE/runtime-schema-migrations.md +44 -0
  8. data/docs/de-DE/validation-matrix.md +42 -4
  9. data/docs/en-US/README.md +1 -0
  10. data/docs/en-US/oql-sql.md +41 -0
  11. data/docs/en-US/project-structure.md +192 -2
  12. data/docs/en-US/runtime-ruby.md +100 -0
  13. data/docs/en-US/runtime-schema-migrations.md +41 -0
  14. data/docs/en-US/validation-matrix.md +39 -4
  15. data/docs/pt-BR/README.md +1 -0
  16. data/docs/pt-BR/entity-dsl.md +8 -0
  17. data/docs/pt-BR/oql-sql.md +41 -0
  18. data/docs/pt-BR/project-structure.md +197 -2
  19. data/docs/pt-BR/runtime-ruby.md +108 -0
  20. data/docs/pt-BR/runtime-schema-migrations.md +40 -0
  21. data/docs/pt-BR/validation-matrix.md +42 -5
  22. data/docs/pt-BR/writing.md +28 -0
  23. data/lib/mxrb/cli/help.rb +426 -0
  24. data/lib/mxrb/cli/release.rb +137 -0
  25. data/lib/mxrb/compiler/compatibility_analyzer.rb +1 -1
  26. data/lib/mxrb/compiler/data_grid_bundle_compiler.rb +8 -1
  27. data/lib/mxrb/compiler/domain_document_compiler.rb +3 -0
  28. data/lib/mxrb/compiler/gallery_bundle_compiler.rb +11 -1
  29. data/lib/mxrb/compiler/generic_widget_bundle_compiler.rb +76 -19
  30. data/lib/mxrb/compiler/legacy_custom_widget_compiler.rb +159 -0
  31. data/lib/mxrb/compiler/legacy_data_grid_compiler.rb +43 -7
  32. data/lib/mxrb/compiler/legacy_page_builder.rb +1248 -23
  33. data/lib/mxrb/compiler/packager.rb +27 -13
  34. data/lib/mxrb/compiler/page_bundle_compiler.rb +13 -4
  35. data/lib/mxrb/compiler/portable_packager.rb +16 -11
  36. data/lib/mxrb/compiler/web_bundle_builder.rb +5 -1
  37. data/lib/mxrb/compiler/web_shell_materializer.rb +42 -2
  38. data/lib/mxrb/compiler/widget_package_extractor.rb +3 -0
  39. data/lib/mxrb/domain_diagram/lifecycle.rb +272 -0
  40. data/lib/mxrb/domain_diagram.rb +409 -0
  41. data/lib/mxrb/dsl/builder.rb +62 -11
  42. data/lib/mxrb/environment.rb +226 -0
  43. data/lib/mxrb/exporter.rb +265 -45
  44. data/lib/mxrb/http/server.rb +107 -0
  45. data/lib/mxrb/initializer.rb +80 -9
  46. data/lib/mxrb/integrity/validator.rb +32 -7
  47. data/lib/mxrb/io/bson_codec.rb +42 -1
  48. data/lib/mxrb/io/mpr_file.rb +146 -3
  49. data/lib/mxrb/model/attribute.rb +3 -0
  50. data/lib/mxrb/model/entity.rb +44 -2
  51. data/lib/mxrb/model/microflow.rb +3 -1
  52. data/lib/mxrb/model/module.rb +71 -0
  53. data/lib/mxrb/model/page.rb +126 -15
  54. data/lib/mxrb/model/project.rb +3 -0
  55. data/lib/mxrb/modeler/catalog.rb +178 -0
  56. data/lib/mxrb/official_marketplace/module_package_importer.rb +23 -12
  57. data/lib/mxrb/official_marketplace/widget_package_installer.rb +15 -11
  58. data/lib/mxrb/official_marketplace.rb +9 -3
  59. data/lib/mxrb/oql/reverse_translator.rb +394 -0
  60. data/lib/mxrb/oql/server.rb +4 -8
  61. data/lib/mxrb/oql.rb +32 -4
  62. data/lib/mxrb/progress.rb +242 -0
  63. data/lib/mxrb/project_lifecycle.rb +5 -2
  64. data/lib/mxrb/ruby_app/exporter.rb +1749 -0
  65. data/lib/mxrb/ruby_app/preset.rb +506 -0
  66. data/lib/mxrb/ruby_app/session_manager.rb +122 -0
  67. data/lib/mxrb/ruby_app.rb +1361 -0
  68. data/lib/mxrb/runtime/access_control.rb +408 -0
  69. data/lib/mxrb/runtime/native.rb +1015 -93
  70. data/lib/mxrb/runtime/scheduler.rb +396 -0
  71. data/lib/mxrb/runtime/schema_migrator.rb +598 -0
  72. data/lib/mxrb/runtime/shared_store.rb +335 -0
  73. data/lib/mxrb/runtime/sqlite_store.rb +729 -0
  74. data/lib/mxrb/scaffold/generator.rb +11 -3
  75. data/lib/mxrb/team_server.rb +10 -7
  76. data/lib/mxrb/uml/activity_diagram.rb +118 -0
  77. data/lib/mxrb/uml/class_diagram.rb +118 -0
  78. data/lib/mxrb/uml/sequence_diagram.rb +112 -0
  79. data/lib/mxrb/uml/server.rb +146 -0
  80. data/lib/mxrb/uml/support.rb +35 -0
  81. data/lib/mxrb/version.rb +1 -1
  82. data/lib/mxrb/web_ui/assets/abnfDiagram-N423BO3Z-C4JD3cEC.js +1 -0
  83. data/lib/mxrb/web_ui/assets/arc-3Z53kgFp.js +1 -0
  84. data/lib/mxrb/web_ui/assets/architecture-TIHT7OUA-BStBTLea.js +1 -0
  85. data/lib/mxrb/web_ui/assets/architectureDiagram-T3A2C74G-DBwqnM4J.js +36 -0
  86. data/lib/mxrb/web_ui/assets/array-BifhSqXX.js +1 -0
  87. data/lib/mxrb/web_ui/assets/blockDiagram-VBNYF7ZC-DLtuHt-j.js +132 -0
  88. data/lib/mxrb/web_ui/assets/c4Diagram-5PPSVZJV-ConSk_pe.js +10 -0
  89. data/lib/mxrb/web_ui/assets/channel-BhPvD7Dt.js +1 -0
  90. data/lib/mxrb/web_ui/assets/chunk-2GRJ4B5K-D2HpWsf_.js +1 -0
  91. data/lib/mxrb/web_ui/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  92. data/lib/mxrb/web_ui/assets/chunk-4I5QYGJK-CiIdNwaF.js +1 -0
  93. data/lib/mxrb/web_ui/assets/chunk-5RXB4S5H-Bu_AaEkE.js +231 -0
  94. data/lib/mxrb/web_ui/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  95. data/lib/mxrb/web_ui/assets/chunk-6Q2QTUOP-wQkNmt_X.js +88 -0
  96. data/lib/mxrb/web_ui/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
  97. data/lib/mxrb/web_ui/assets/chunk-GF5L2VYU-G8MmiBzU.js +206 -0
  98. data/lib/mxrb/web_ui/assets/chunk-I66GZJ75-CulcrLPh.js +127 -0
  99. data/lib/mxrb/web_ui/assets/chunk-JQJVKLGR-ukjzcdlL.js +156 -0
  100. data/lib/mxrb/web_ui/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  101. data/lib/mxrb/web_ui/assets/chunk-KBJHAD2P-Wm35lYov.js +1 -0
  102. data/lib/mxrb/web_ui/assets/chunk-KEIR6QF5-BICK3FdT.js +161 -0
  103. data/lib/mxrb/web_ui/assets/chunk-NSK5VX7P-D2nYrCY4.js +2 -0
  104. data/lib/mxrb/web_ui/assets/chunk-QR6OTTB3-BrdIzg4Q.js +62 -0
  105. data/lib/mxrb/web_ui/assets/chunk-RYQCIY6F-CCfZMcW6.js +1 -0
  106. data/lib/mxrb/web_ui/assets/chunk-UBXNYLIW-BPEVYsFK.js +1 -0
  107. data/lib/mxrb/web_ui/assets/chunk-W5SLKNZC-2E6OPhA4.js +1 -0
  108. data/lib/mxrb/web_ui/assets/chunk-WRU74C26-CBIvOyWp.js +70 -0
  109. data/lib/mxrb/web_ui/assets/chunk-XXDRQBXY-pH58XAyl.js +1 -0
  110. data/lib/mxrb/web_ui/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  111. data/lib/mxrb/web_ui/assets/classDiagram-JCYQIIEL-xiqo4D89.js +1 -0
  112. data/lib/mxrb/web_ui/assets/classDiagram-v2-OCEON4UE-xiqo4D89.js +1 -0
  113. data/lib/mxrb/web_ui/assets/cose-bilkent-JH36ORCC-t_flPpii.js +1 -0
  114. data/lib/mxrb/web_ui/assets/cynefin-VYW2F7L2-BIqktTEv.js +1 -0
  115. data/lib/mxrb/web_ui/assets/cynefinDiagram-MW4NZA55-C2d--lut.js +62 -0
  116. data/lib/mxrb/web_ui/assets/cytoscape.esm-B-NFISlW.js +321 -0
  117. data/lib/mxrb/web_ui/assets/dagre-Buvkdvvj.js +1 -0
  118. data/lib/mxrb/web_ui/assets/dagre-VZM6K2ZE-CSz2_tb5.js +4 -0
  119. data/lib/mxrb/web_ui/assets/defaultLocale-BFoDCU3G.js +1 -0
  120. data/lib/mxrb/web_ui/assets/diagram-7IWD3JNH-DCt7u7V_.js +30 -0
  121. data/lib/mxrb/web_ui/assets/diagram-B4RE2ZJO-DqZBqkMu.js +3 -0
  122. data/lib/mxrb/web_ui/assets/diagram-LBJQPF4R-ClIYbIsI.js +24 -0
  123. data/lib/mxrb/web_ui/assets/diagram-Q27KOJAE-DFxhvthJ.js +24 -0
  124. data/lib/mxrb/web_ui/assets/diagram-UB23O5K3-BSUTXptv.js +41 -0
  125. data/lib/mxrb/web_ui/assets/dist-D2qOEbeJ.js +1 -0
  126. data/lib/mxrb/web_ui/assets/domain-mGOZ1KCG.css +1 -0
  127. data/lib/mxrb/web_ui/assets/domain-qsjXhy4v.js +1 -0
  128. data/lib/mxrb/web_ui/assets/ebnfDiagram-BXEA7PRR-B5reICEi.js +1 -0
  129. data/lib/mxrb/web_ui/assets/erDiagram-JOGREHBK-Co5Mxn2p.js +85 -0
  130. data/lib/mxrb/web_ui/assets/eventmodeling-45OFAUF4-C1ylhSks.js +1 -0
  131. data/lib/mxrb/web_ui/assets/flowDiagram-UKHOOZJN-CHXaJIaS.js +1 -0
  132. data/lib/mxrb/web_ui/assets/ganttDiagram-PKOTCBZU-DPPPaWaz.js +292 -0
  133. data/lib/mxrb/web_ui/assets/gitGraph-TEB2WS4Q-qS77ywA9.js +1 -0
  134. data/lib/mxrb/web_ui/assets/gitGraphDiagram-DS77QQ5N-DpAv6-BT.js +106 -0
  135. data/lib/mxrb/web_ui/assets/graphlib-DS17s2tU.js +1 -0
  136. data/lib/mxrb/web_ui/assets/info-DKCQHKI2-C9VD1Y4e.js +1 -0
  137. data/lib/mxrb/web_ui/assets/infoDiagram-6WML65LV-Bk1kbcCy.js +2 -0
  138. data/lib/mxrb/web_ui/assets/init-C-OQMol4.js +1 -0
  139. data/lib/mxrb/web_ui/assets/ishikawaDiagram-WSZJBQD7-0gcISBFR.js +70 -0
  140. data/lib/mxrb/web_ui/assets/journeyDiagram-NVQOT4AX-Dt0RQnlh.js +139 -0
  141. data/lib/mxrb/web_ui/assets/jsx-runtime-DV0a5kSb.js +9 -0
  142. data/lib/mxrb/web_ui/assets/kanban-definition-27J2QSJJ-DIPQM7w9.js +89 -0
  143. data/lib/mxrb/web_ui/assets/katex-CXMH3UgJ.js +257 -0
  144. data/lib/mxrb/web_ui/assets/line-C0rYD-tL.js +1 -0
  145. data/lib/mxrb/web_ui/assets/linear-BPPxWorj.js +1 -0
  146. data/lib/mxrb/web_ui/assets/map-BaFkSB1l.js +1 -0
  147. data/lib/mxrb/web_ui/assets/mermaid-parser.core-B_UPzTxa.js +7 -0
  148. data/lib/mxrb/web_ui/assets/mindmap-definition-FAOFIHXS-CdJRSMuk.js +96 -0
  149. data/lib/mxrb/web_ui/assets/modeler-BNqHthwk.css +1 -0
  150. data/lib/mxrb/web_ui/assets/modeler-CQmqflbg.js +1 -0
  151. data/lib/mxrb/web_ui/assets/ordinal-BDEzSJ7C.js +1 -0
  152. data/lib/mxrb/web_ui/assets/packet-7NZHBO7P-CwQ4NSBs.js +1 -0
  153. data/lib/mxrb/web_ui/assets/path-fybaL0A-.js +1 -0
  154. data/lib/mxrb/web_ui/assets/pegDiagram-VL7TDLO6-D40S7ppx.js +1 -0
  155. data/lib/mxrb/web_ui/assets/pie-RZYD4A2V-CJroHMKh.js +1 -0
  156. data/lib/mxrb/web_ui/assets/pieDiagram-7S7Q4E2Y-QB54__BV.js +39 -0
  157. data/lib/mxrb/web_ui/assets/quadrantDiagram-CIZ2JOQS-DBhDS6ag.js +7 -0
  158. data/lib/mxrb/web_ui/assets/radar-I7S5WNFK-DdIUwd2d.js +1 -0
  159. data/lib/mxrb/web_ui/assets/railroad-3IZDKUUU-B1Lw6Xyq.js +1 -0
  160. data/lib/mxrb/web_ui/assets/railroad-abnf-AHOZXSZD-CQgQoeak.js +1 -0
  161. data/lib/mxrb/web_ui/assets/railroad-ebnf-EBAXGLYW-D7qEQ2x8.js +1 -0
  162. data/lib/mxrb/web_ui/assets/railroad-peg-LSFZ7HO6-C005Vxbb.js +1 -0
  163. data/lib/mxrb/web_ui/assets/railroadDiagram-AXF67PYL-BBP2qk7M.js +1 -0
  164. data/lib/mxrb/web_ui/assets/requirementDiagram-LRYGKXZP-D2cLsAoI.js +84 -0
  165. data/lib/mxrb/web_ui/assets/rough.esm-Dy-Kn_BL.js +1 -0
  166. data/lib/mxrb/web_ui/assets/sankeyDiagram-W5VNT64P-Bpi3FiP2.js +40 -0
  167. data/lib/mxrb/web_ui/assets/sequenceDiagram-SI44F4Z6-DCRcDlVe.js +162 -0
  168. data/lib/mxrb/web_ui/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
  169. data/lib/mxrb/web_ui/assets/src-CvxfUak2.js +1 -0
  170. data/lib/mxrb/web_ui/assets/stateDiagram-OKZ733FA-C2GCEoxl.js +1 -0
  171. data/lib/mxrb/web_ui/assets/stateDiagram-v2-UEYNNEHI-D2gWgedh.js +1 -0
  172. data/lib/mxrb/web_ui/assets/swimlanes-SLNWSIFB-CRI9irEW.js +2 -0
  173. data/lib/mxrb/web_ui/assets/swimlanesDiagram-ULZ7WXOC-C5Wb_-hZ.js +8 -0
  174. data/lib/mxrb/web_ui/assets/theme-_mxc7BdY.css +1 -0
  175. data/lib/mxrb/web_ui/assets/timeline-definition-Z64GVDOM-B1-mNg-h.js +120 -0
  176. data/lib/mxrb/web_ui/assets/treeView-QDETBFTQ-7CTvIwKq.js +1 -0
  177. data/lib/mxrb/web_ui/assets/treemap-6X3UGDF4-CtL22pq5.js +1 -0
  178. data/lib/mxrb/web_ui/assets/uml-C3KAFGTq.js +51 -0
  179. data/lib/mxrb/web_ui/assets/uml-Dn-qd01X.css +1 -0
  180. data/lib/mxrb/web_ui/assets/vennDiagram-T6HMQDX7-BzVnUEAy.js +34 -0
  181. data/lib/mxrb/web_ui/assets/wardley-OPB4EBWU-JNi-QNag.js +1 -0
  182. data/lib/mxrb/web_ui/assets/wardleyDiagram-T6FBY63Y-BUwmAQ-B.js +78 -0
  183. data/lib/mxrb/web_ui/assets/xychartDiagram-ELKLHX3M-DEQS8sKp.js +7 -0
  184. data/lib/mxrb/web_ui/domain.html +15 -0
  185. data/lib/mxrb/web_ui/modeler.html +16 -0
  186. data/lib/mxrb/web_ui/uml.html +31 -0
  187. data/lib/mxrb/web_ui.rb +89 -0
  188. data/lib/mxrb/widget_certification.rb +208 -0
  189. data/lib/mxrb/writer.rb +278 -41
  190. data/lib/mxrb.rb +38 -8
  191. metadata +157 -5
@@ -25,7 +25,9 @@ module Mxrb
25
25
  @index = source.document_index
26
26
  @widget_type = find_widget_type
27
27
  @values = property_values(widget['Object'])
28
- @list_data_source = @values.values.find { _1.first == 'DataSource' }&.last
28
+ @list_data_source = @values.values.find do |type, value, _metadata|
29
+ type == 'DataSource' && database_list_property(value)
30
+ end&.last
29
31
  @component_name = widget_id.to_s.split('.').last
30
32
  @module_path = widget_id.to_s.tr('.', '/')
31
33
  end
@@ -60,60 +62,105 @@ module Mxrb
60
62
  type = @index[IO::BsonCodec.extract_id(property['TypePointer'])]
61
63
  next unless type
62
64
 
63
- [type.fetch('PropertyKey'), [type.dig('ValueType', 'Type'), property['Value']]]
65
+ value_type = type.fetch('ValueType', {})
66
+ [type.fetch('PropertyKey'), [value_type['Type'], property['Value'], value_type]]
64
67
  end.to_h
65
68
  end
66
69
 
67
70
  def supported_property?(pair)
68
- type, value = pair
71
+ type, value, metadata = pair
69
72
  return true if %w[Boolean Integer Decimal Enumeration String System].include?(type)
70
73
  return value['DataSource'].nil? || !database_list_property(value).nil? if type == 'DataSource'
71
74
  return true if type == 'Association'
72
75
  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)
76
+ return true if type == 'Selection'
77
+ return value['Icon'].nil? if type == 'Icon'
78
+ return value['Image'].to_s.empty? if type == 'Image'
79
+ return true if %w[Expression TextTemplate].include?(type) && expression_property(type, value, metadata)
80
+ return true if type == 'Attribute' && attribute_property(value, metadata)
75
81
  return no_action?(value) || !compiled_action(value).nil? if type == 'Action'
76
82
  return array(value['Widgets']).empty? || @render_widgets if type == 'Widgets'
77
- return array(value['Objects']).empty? if type == 'Object'
83
+ return supported_objects?(value) if type == 'Object'
78
84
 
79
85
  false
80
86
  end
81
87
 
82
88
  def compile_property(pair)
83
- type, value = pair
89
+ type, value, metadata = pair
84
90
  case type
85
91
  when 'Boolean' then value['PrimitiveValue'] == 'true'
86
92
  when 'Integer' then value['PrimitiveValue'].to_i
87
93
  when 'Decimal' then value['PrimitiveValue'].to_f
88
94
  when 'Enumeration', 'String' then value['PrimitiveValue'].to_s
89
- when 'Expression', 'TextTemplate' then raw(expression_property(type, value))
95
+ when 'Expression', 'TextTemplate' then raw(expression_property(type, value, metadata))
90
96
  when 'DataSource' then database_list_property(value)&.then { raw(_1) } || :undefined
91
97
  when 'Action' then compiled_action(value)&.then { raw(_1) } || :undefined
92
98
  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 []
99
+ when 'Attribute' then attribute_property(value, metadata)&.then { raw(_1) } || :undefined
100
+ when 'Selection' then raw(selection_property(value))
101
+ when 'Icon', 'Image' then :undefined
102
+ when 'Widgets' then compile_widgets(value, metadata)
103
+ when 'Object' then compile_objects(value)
96
104
  else :undefined
97
105
  end
98
106
  end
99
107
 
100
- def compile_widgets(value)
108
+ def compile_widgets(value, metadata = nil)
101
109
  widgets = array(value['Widgets'])
102
110
  return [] if widgets.empty?
103
111
 
104
- @render_widgets.call(widgets)
112
+ rendered = @render_widgets.call(widgets)
113
+ return rendered unless data_source_bound?(metadata)
114
+
115
+ raw("TemplatedWidgetProperty(#{javascript(
116
+ children: raw("() => #{javascript(rendered)}"), dataSourceId: widget_key, editable: false
117
+ )})")
118
+ end
119
+
120
+ def supported_objects?(value)
121
+ array(value['Objects']).all? do |object|
122
+ values = property_values(object)
123
+ !values.empty? && values.values.all? { supported_property?(_1) }
124
+ end
105
125
  end
106
126
 
107
- def expression_property(type, value)
127
+ def compile_objects(value)
128
+ array(value['Objects']).map do |object|
129
+ values = property_values(object)
130
+ with_list_data_source(values) do
131
+ values.each_with_object({}) do |(key, pair), result|
132
+ compiled = compile_property(pair)
133
+ result[key.to_sym] = compiled unless compiled.equal?(:undefined)
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def with_list_data_source(values)
140
+ previous = @list_data_source
141
+ @list_data_source = values.values.find do |type, value, _metadata|
142
+ type == 'DataSource' && database_list_property(value)
143
+ end&.last
144
+ yield
145
+ ensure
146
+ @list_data_source = previous
147
+ end
148
+
149
+ def selection_property(value)
150
+ "SelectionProperty(#{javascript(selectionType: value.fetch('Selection', 'None'), dataSourceId: widget_key)})"
151
+ end
152
+
153
+ def expression_property(type, value, metadata = nil)
108
154
  expression = type == 'Expression' ? compile_expression(value['Expression']) : compile_template(value)
109
155
  return unless expression
110
156
 
157
+ list_bound = data_source_bound?(metadata) && @list_data_source
111
158
  args = expression_variables(expression).to_h do |variable|
112
- [variable.to_sym, { widget: @scope, source: 'object' }]
159
+ [variable.to_sym, { widget: list_bound ? widget_key : @scope, source: 'object' }]
113
160
  end
114
- property = @list_data_source ? 'ListExpressionProperty' : 'ExpressionProperty'
161
+ property = list_bound ? 'ListExpressionProperty' : 'ExpressionProperty'
115
162
  config = { expression: { expr: expression, args: } }
116
- config[:dataSourceId] = widget_key if @list_data_source
163
+ config[:dataSourceId] = widget_key if list_bound
117
164
  "#{property}(#{javascript(config)})"
118
165
  end
119
166
 
@@ -200,11 +247,17 @@ module Mxrb
200
247
  pieces.compact.reduce { |left, right| { type: 'function', name: '+', parameters: [left, right] } }
201
248
  end
202
249
 
203
- def attribute_property(value)
250
+ def attribute_property(value, metadata = nil)
204
251
  attribute = value.dig('AttributeRef', 'Attribute').to_s
205
- return unless @scope && attribute.include?('.')
252
+ return unless (@scope || @list_data_source) && attribute.include?('.')
206
253
 
207
254
  entity, _, name = attribute.rpartition('.')
255
+ if data_source_bound?(metadata) && @list_data_source
256
+ return "ListAttributeProperty(#{javascript(
257
+ path: '', entity:, attribute: name, dataSourceId: widget_key, isList: false
258
+ )})"
259
+ end
260
+
208
261
  "AttributeProperty(#{javascript(
209
262
  scope: @scope, path: '', entity:, attribute: name,
210
263
  onChange: { type: 'doNothing', argMap: {}, config: {}, disabledDuringExecution: true },
@@ -212,6 +265,10 @@ module Mxrb
212
265
  )})"
213
266
  end
214
267
 
268
+ def data_source_bound?(metadata)
269
+ !metadata.to_h.fetch('DataSourceProperty', '').to_s.empty?
270
+ end
271
+
215
272
  def expression_variables(expression)
216
273
  case expression
217
274
  when Hash
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zip'
4
+
5
+ module Mxrb
6
+ module Compiler
7
+ # Serializes schema-described Mendix 6/7 custom widgets to their Dojo client contract.
8
+ # rubocop:disable Metrics
9
+ class LegacyCustomWidgetCompiler
10
+ include ModelValues
11
+
12
+ SUPPORTED_TYPES = %w[
13
+ Attribute Boolean Decimal Entity EntityConstraint Enumeration Form Image Integer
14
+ Microflow String System TranslatableString Object
15
+ ].freeze
16
+
17
+ attr_reader :widget_id
18
+
19
+ def initialize(source, widget, language:)
20
+ @source = source
21
+ @widget = widget
22
+ @language = language
23
+ @widget_type = widget['Type'] || {}
24
+ @widget_id = @widget_type['WidgetId'].to_s
25
+ @schema_index = schema_index(@widget_type)
26
+ end
27
+
28
+ def supported?
29
+ !widget_id.empty? && package_module? && properties(@widget['Object']).all? do |property|
30
+ type = property_type(property)
31
+ SUPPORTED_TYPES.include?(type) && supported_value?(type, property['Value'])
32
+ end
33
+ end
34
+
35
+ def properties_hash
36
+ properties(@widget['Object']).to_h do |property|
37
+ schema = property_schema(property)
38
+ [schema['PropertyKey'].to_s, compile_value(schema.dig('ValueType', 'Type'), property['Value'])]
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def properties(object) = array(object&.fetch('Properties', nil))
45
+
46
+ def property_schema(property)
47
+ @schema_index[IO::BsonCodec.extract_id(property['TypePointer'])] || {}
48
+ end
49
+
50
+ def property_type(property) = property_schema(property).dig('ValueType', 'Type').to_s
51
+
52
+ def supported_value?(type, value)
53
+ value ||= {}
54
+ return objects(value).all? { supported_object?(_1) } if type == 'Object'
55
+ return !image_uri(value['Image']).nil? if type == 'Image' && !value['Image'].to_s.empty?
56
+
57
+ true
58
+ end
59
+
60
+ def supported_object?(object)
61
+ properties(object).all? do |property|
62
+ type = property_type(property)
63
+ SUPPORTED_TYPES.include?(type) && supported_value?(type, property['Value'])
64
+ end
65
+ end
66
+
67
+ def compile_value(type, value)
68
+ value ||= {}
69
+ case type
70
+ when 'Boolean' then value['PrimitiveValue'] == 'true'
71
+ when 'Integer' then value['PrimitiveValue'].to_i
72
+ when 'Decimal' then value['PrimitiveValue'].to_f
73
+ when 'Enumeration', 'String', 'System' then value['PrimitiveValue'].to_s
74
+ when 'TranslatableString' then translated(value['TranslatableValue'])
75
+ when 'Entity' then value['EntityPath'].to_s
76
+ when 'EntityConstraint' then value['XPathConstraint'].to_s
77
+ when 'Attribute' then relative_attribute(value['AttributePath'])
78
+ when 'Microflow' then value['Microflow'].to_s
79
+ when 'Form' then value['Form'].to_s
80
+ when 'Image' then value['Image'].to_s.empty? ? '' : image_uri(value['Image'])
81
+ when 'Object' then objects(value).map { compile_object(_1) }
82
+ end
83
+ end
84
+
85
+ def compile_object(object)
86
+ properties(object).to_h do |property|
87
+ schema = property_schema(property)
88
+ [schema['PropertyKey'].to_s, compile_value(schema.dig('ValueType', 'Type'), property['Value'])]
89
+ end
90
+ end
91
+
92
+ def objects(value) = array(value&.fetch('Objects', nil))
93
+
94
+ def relative_attribute(path)
95
+ value = path.to_s
96
+ value.split('.').last.to_s
97
+ end
98
+
99
+ def translated(text)
100
+ items = array(text&.fetch('Items', nil))
101
+ items.find { _1['LanguageCode'] == @language }&.fetch('Text', '') ||
102
+ items.find { _1['LanguageCode'] == 'en_US' }&.fetch('Text', '') ||
103
+ items.first&.fetch('Text', '') || ''
104
+ end
105
+
106
+ def image_uri(reference)
107
+ module_name, collection_name, image_name = reference.to_s.split('.', 3)
108
+ unit = @source.units_of('Images$ImageCollection').find do |candidate|
109
+ candidate.module_name == module_name && candidate.document['Name'] == collection_name
110
+ end
111
+ image = array(unit&.document&.fetch('Images', nil)).find { _1['Name'] == image_name }
112
+ unless image
113
+ return system_image_uri(collection_name, image_name) if module_name == 'System'
114
+
115
+ return
116
+ end
117
+
118
+ "img/#{module_name}$#{image_name}.#{image_format(image)}"
119
+ end
120
+
121
+ def system_image_uri(collection_name, image_name)
122
+ return if collection_name != 'Images' || image_name.to_s.empty?
123
+
124
+ extension = %w[Error Running Completed Module].include?(image_name) ? 'gif' : 'png'
125
+ "img/System$#{image_name}.#{extension}"
126
+ end
127
+
128
+ def schema_index(root)
129
+ index = {}
130
+ visit = lambda do |value|
131
+ case value
132
+ when Hash
133
+ id = IO::BsonCodec.extract_id(value['$ID'])
134
+ index[id] = value if id
135
+ value.each_value { visit.call(_1) }
136
+ when Array then value.each { visit.call(_1) }
137
+ end
138
+ end
139
+ visit.call(root)
140
+ index
141
+ end
142
+
143
+ def package_module?
144
+ return true unless @source.is_a?(SourceModel)
145
+
146
+ entry = "#{widget_id.tr('.', '/')}.js"
147
+ root = File.join(File.dirname(@source.path), 'widgets')
148
+ return true if File.file?(File.join(root, entry))
149
+
150
+ Dir.glob(File.join(root, '*.mpk')).any? do |path|
151
+ Zip::File.open(path) { _1.find_entry(entry) }
152
+ rescue Zip::Error
153
+ false
154
+ end
155
+ end
156
+ end
157
+ # rubocop:enable Metrics
158
+ end
159
+ end
@@ -21,7 +21,9 @@ module Mxrb
21
21
  'Forms$GridNewButton' => 'InsertNew',
22
22
  'Forms$GridEditButton' => 'EditSelection',
23
23
  'Forms$GridDeleteButton' => 'DeleteSelection',
24
- 'Forms$GridActionButton' => 'InvokeAction'
24
+ 'Forms$GridActionButton' => 'InvokeAction',
25
+ 'Forms$DataGridAddButton' => 'AddRef',
26
+ 'Forms$DataGridRemoveButton' => 'DeleteRef'
25
27
  }.freeze
26
28
 
27
29
  attr_reader :unsupported
@@ -38,16 +40,18 @@ module Mxrb
38
40
  def html
39
41
  props = JSON.generate(properties)[1..-2]
40
42
  attributes = {
41
- 'data-mendix-id' => widget_id, 'data-mendix-type' => 'mxui.widget.DataGrid',
43
+ 'data-mendix-id' => widget_id, 'data-mendix-type' => client_widget_type,
42
44
  'data-mendix-props' => props, 'class' => css_class, 'tabindex' => @widget['TabIndex'].to_i
43
45
  }
44
46
  "<div #{attributes.map { |key, value| "#{key}='#{escape(value)}'" }.join(' ')}></div>"
45
47
  end
46
48
 
49
+ def id = widget_id
50
+
47
51
  private
48
52
 
49
53
  def properties
50
- {
54
+ result = {
51
55
  'friendlyId' => friendly_id, 'entity' => entity, 'schema' => entity_schema,
52
56
  'config' => {
53
57
  'locale' => @language, 'gridType' => 'Standalone',
@@ -57,11 +61,14 @@ module Mxrb
57
61
  'griddata' => columns.map { compile_column(_1) }, 'plugins' => {}
58
62
  }
59
63
  }
64
+ result['xpathConstraint'] = @widget['SelectableXPathConstraint'].to_s if reference_set?
65
+ result
60
66
  end
61
67
 
62
68
  def presentation
63
69
  {
64
- 'rows' => @widget['NumberOfRows'].to_i, 'columns' => 1,
70
+ 'rows' => @widget['NumberOfRows'].to_i,
71
+ 'columns' => template_grid? ? [@widget['NumberOfColumns'].to_i, 1].max : 1,
65
72
  'controlbar' => @widget['IsControlBarVisible'] == true,
66
73
  'pagingbar' => @widget['IsPagingEnabled'] == true,
67
74
  'sortparams' => sort_items, 'searchbar' => !search_elements.empty?,
@@ -74,6 +81,8 @@ module Mxrb
74
81
  end
75
82
 
76
83
  def datasource
84
+ return reference_set_datasource if reference_set?
85
+
77
86
  type = SOURCE_TYPES[data_source['$Type']]
78
87
  @unsupported << data_source['$Type'].to_s unless type
79
88
  result = { 'friendlyId' => friendly_id, 'type' => type || 'unsupported', 'path' => entity }
@@ -136,7 +145,7 @@ module Mxrb
136
145
 
137
146
  def button_params(button)
138
147
  case button['$Type']
139
- when 'Forms$GridNewButton', 'Forms$GridEditButton'
148
+ when 'Forms$GridNewButton', 'Forms$GridEditButton', 'Forms$DataGridAddButton'
140
149
  { entity => { 'pageSettings' => page_settings(button['FormSettings']) } }
141
150
  when 'Forms$GridActionButton'
142
151
  { entity => { 'action' => client_action(button['Action']),
@@ -200,7 +209,17 @@ module Mxrb
200
209
 
201
210
  def entity
202
211
  direct = data_source['EntityPath'] || data_source.dig('EntityRef', 'Entity')
203
- direct.to_s.empty? ? attribute_path(columns.first).to_s.rpartition('.').first : direct.to_s
212
+ value = direct.to_s.empty? ? attribute_path(columns.first).to_s.rpartition('.').first : direct.to_s
213
+ reference_set? ? value.split('/').last.to_s : value
214
+ end
215
+
216
+ def reference_set_datasource
217
+ {
218
+ 'friendlyId' => friendly_id, 'path' => entity, 'entity' => entity,
219
+ 'reference' => data_source['EntityPath'].to_s.split('/').first.to_s,
220
+ 'sort' => sort_items, 'attributes' => columns.map { relative_attribute(attribute_path(_1)) },
221
+ 'distinct' => false
222
+ }
204
223
  end
205
224
 
206
225
  def entity_schema
@@ -233,10 +252,27 @@ module Mxrb
233
252
  def widget_id = "#{@sequence}_#{buttons.length}"
234
253
 
235
254
  def css_class
236
- ['mx-datagrid', "mx-name-#{@widget['Name']}", @widget['Class']]
255
+ [widget_css_class, "mx-name-#{@widget['Name']}", @widget['Class']]
237
256
  .map(&:to_s).reject(&:empty?).uniq.join(' ')
238
257
  end
239
258
 
259
+ def client_widget_type
260
+ return 'mxui.widget.TemplateGrid' if template_grid?
261
+ return 'mxui.widget.ReferenceSetSelector' if reference_set?
262
+
263
+ 'mxui.widget.DataGrid'
264
+ end
265
+
266
+ def widget_css_class
267
+ return 'mx-templategrid' if template_grid?
268
+ return 'mx-referencesetselector' if reference_set?
269
+
270
+ 'mx-datagrid'
271
+ end
272
+
273
+ def template_grid? = @widget['$Type'] == 'Forms$TemplateGrid'
274
+ def reference_set? = @widget['$Type'] == 'Forms$ReferenceSetSelector'
275
+
240
276
  def translated(text)
241
277
  items = array(text&.fetch('Items', nil))
242
278
  items.find { _1['LanguageCode'] == @language }&.fetch('Text', '') ||