plugins/sync.js

  1. const base = require("./base");
  2. const ripe = base.ripe;
  3. /**
  4. * @class
  5. * @augments Plugin
  6. * @classdesc Plugin responsible for applying synchronization rules.
  7. *
  8. * @param {Object} rules A Map with synchronization rules to be applied.
  9. * If defined, overrides the rules defined on the model's config.
  10. * @param {Object} options An object with options to configure the plugin.
  11. */
  12. ripe.Ripe.plugins.SyncPlugin = function(rules, options = {}) {
  13. ripe.Ripe.plugins.Plugin.call(this);
  14. this.rules = this._normalizeRules(rules);
  15. this.manual = Boolean(options.manual || rules);
  16. this.auto = !this.manual;
  17. };
  18. ripe.Ripe.plugins.SyncPlugin.prototype = ripe.build(ripe.Ripe.plugins.Plugin.prototype);
  19. ripe.Ripe.plugins.SyncPlugin.prototype.constructor = ripe.Ripe.plugins.SyncPlugin;
  20. /**
  21. * The Sync Plugin binds the 'post_config' and 'part' events,
  22. * in order to:
  23. * - retrieve the model's configuration.
  24. * - change the necessary parts making them comply with the syncing rules.
  25. *
  26. * @param {Ripe} The Ripe instance in use.
  27. */
  28. ripe.Ripe.plugins.SyncPlugin.prototype.register = function(owner) {
  29. ripe.Ripe.plugins.Plugin.prototype.register.call(this, owner);
  30. // sets the initial set of rules from the owner, in case the
  31. // auto mode is enabled for the current instance
  32. this.rules =
  33. this.auto && owner.loadedConfig
  34. ? this._normalizeRules(owner.loadedConfig.sync)
  35. : this.rules;
  36. // listens for model changes and if the load config option is
  37. // set then retrieves the new model's post config, otherwise
  38. // unregisters itself as its rules are no longer valid
  39. this._postConfigBind = this.manual
  40. ? null
  41. : this.owner.bind("post_config", config => {
  42. this.rules = config ? this._normalizeRules(config.sync) : {};
  43. });
  44. // binds to the part event to change the necessary parts
  45. // so that they comply with the product's sync rules
  46. this._partBind = this.owner.bind("part", this._applySync.bind(this));
  47. };
  48. /**
  49. * The unregister to be called (by the owner)
  50. * the plugins unbinds events and executes
  51. * any necessary cleanup operation.
  52. *
  53. * @param {Ripe} The Ripe instance in use.
  54. */
  55. ripe.Ripe.plugins.SyncPlugin.prototype.unregister = function(owner) {
  56. this.owner && this.owner.unbind("part", this._partBind);
  57. this.owner && this.owner.unbind("post_config", this._postConfigBind);
  58. ripe.Ripe.plugins.Plugin.prototype.unregister.call(this, owner);
  59. };
  60. /**
  61. * Traverses the provided rules and transforms string rules
  62. * into object rules to keep the internal representation
  63. * of the rules consistent.
  64. *
  65. * @param {Array} rules The rules that will be normalized
  66. * into object rules.
  67. * @returns {Object} The normalized version of the rules.
  68. *
  69. * @ignore
  70. */
  71. ripe.Ripe.plugins.SyncPlugin.prototype._normalizeRules = function(rules) {
  72. const _rules = {};
  73. if (!rules) {
  74. return _rules;
  75. }
  76. for (const ruleName in rules) {
  77. const rule = rules[ruleName];
  78. for (let index = 0; index < rule.length; index++) {
  79. let part = rule[index];
  80. if (typeof part === "string") {
  81. part = {
  82. part: part
  83. };
  84. rule[index] = part;
  85. }
  86. }
  87. _rules[ruleName] = rule;
  88. }
  89. return _rules;
  90. };
  91. /**
  92. * Checks if any of the sync rules should apply to the
  93. * provided part, meaning that the other parts of the rule
  94. * have to be changed accordingly.
  95. *
  96. * @param {String} name The name of the part that may be
  97. * affected by a rule.
  98. * @param {Object} value The material and color of the part
  99. * as a object map.
  100. *
  101. * @ignore
  102. */
  103. ripe.Ripe.plugins.SyncPlugin.prototype._applySync = function(name, value) {
  104. // iterates over the complete set of rules to determine
  105. // if any of them should apply to the provided part
  106. for (const key in this.rules) {
  107. // if a part was selected and it is part of
  108. // the rule then its value is used otherwise
  109. // the first part of the rule is used
  110. const rule = this.rules[key];
  111. const firstPart = rule[0];
  112. name = name || firstPart.part;
  113. value = value || this.owner.parts[name];
  114. // checks if the part triggers the sync rule
  115. // and skips to the next rule if it doesn't
  116. if (this._shouldSync(rule, name, value) === false) {
  117. continue;
  118. }
  119. // iterates through the parts of the rule and
  120. // sets their material and color according to
  121. // the sync rule in case there's a match
  122. for (let index = 0; index < rule.length; index++) {
  123. const _part = rule[index];
  124. // in case the current rule definition references the current
  125. // part in rule deinition, ignores the current loop
  126. if (_part.part === name) {
  127. continue;
  128. }
  129. // tries to find the target part configuration an in case
  130. // no such part is found throws an error
  131. const target = this.owner.parts[_part.part];
  132. if (!target) {
  133. throw new Error(`Target part for rule not found '${_part.part}'`);
  134. }
  135. if (_part.color === undefined) {
  136. target.material = _part.material ? _part.material : value.material;
  137. }
  138. target.color = _part.color ? _part.color : value.color;
  139. }
  140. }
  141. };
  142. /**
  143. * Checks if the sync rule contains the provided part
  144. * meaning that the other parts of the rule have to
  145. * be changed accordingly.
  146. *
  147. * @param {Object} rule The sync rule that will be checked.
  148. * @param {String} name The name of the part that may be
  149. * affected by the rule.
  150. * @param {Object} value The material and color of the part.
  151. * @returns {Boolean} If the provided rule is valid for the provided
  152. * part and value (material and color).
  153. *
  154. * @ignore
  155. */
  156. ripe.Ripe.plugins.SyncPlugin.prototype._shouldSync = function(rule, name, value) {
  157. for (let index = 0; index < rule.length; index++) {
  158. const rulePart = rule[index];
  159. const part = rulePart.part;
  160. const material = rulePart.material;
  161. const color = rulePart.color;
  162. const materialSync = !material || material === value.material;
  163. const colorSync = !color || color === value.color;
  164. if (part === name && materialSync && colorSync) {
  165. return true;
  166. }
  167. }
  168. return false;
  169. };