All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

228 lines
7.6 KiB

  1. import { getTokenConfig, setTokenConfig } from '../scripts/utils.js';
  2. export default class TokenCustomConfig extends TokenConfig {
  3. constructor(object, options, imgSrc, imgName, callback, config) {
  4. let token;
  5. if (object instanceof Actor) {
  6. token = new TokenDocument(object.token, {
  7. actor: object,
  8. });
  9. } else {
  10. token = new TokenDocument(object.document, {
  11. actor: game.actors.get(object.actorId),
  12. });
  13. }
  14. super(token, options);
  15. this.imgSrc = imgSrc;
  16. this.imgName = imgName;
  17. this.callback = callback;
  18. this.config = config;
  19. if (this.config) {
  20. this.flags = this.config.flags;
  21. this.tv_script = this.config.tv_script;
  22. }
  23. }
  24. _getSubmitData(updateData = {}) {
  25. if (!this.form) throw new Error('The FormApplication subclass has no registered form element');
  26. const fd = new FormDataExtended(this.form, { editors: this.editors });
  27. let data = fd.object;
  28. if (updateData) data = foundry.utils.flattenObject(foundry.utils.mergeObject(data, updateData));
  29. // Clear detection modes array
  30. if (!('detectionModes.0.id' in data)) data.detectionModes = [];
  31. // Treat "None" as null for bar attributes
  32. data['bar1.attribute'] ||= null;
  33. data['bar2.attribute'] ||= null;
  34. return data;
  35. }
  36. async _updateObject(event, formData) {
  37. const filtered = {};
  38. const form = $(event.target).closest('form');
  39. form.find('.form-group').each(function (_) {
  40. const tva_checkbox = $(this).find('.tva-config-checkbox > input');
  41. if (tva_checkbox.length && tva_checkbox.is(':checked')) {
  42. $(this)
  43. .find('[name]')
  44. .each(function (_) {
  45. const name = $(this).attr('name');
  46. filtered[name] = formData[name];
  47. });
  48. }
  49. });
  50. if (this.tv_script) {
  51. filtered.tv_script = this.tv_script;
  52. }
  53. if (this.config) {
  54. let config = expandObject(filtered);
  55. config.flags = config.flags ? mergeObject(this.flags || {}, config.flags) : this.flags;
  56. if (this.callback) this.callback(config);
  57. } else {
  58. const saved = setTokenConfig(this.imgSrc, this.imgName, filtered);
  59. if (this.callback) this.callback(saved);
  60. }
  61. }
  62. applyCustomConfig() {
  63. const tokenConfig = flattenObject(this.config || getTokenConfig(this.imgSrc, this.imgName));
  64. const form = $(this.form);
  65. for (const key of Object.keys(tokenConfig)) {
  66. const el = form.find(`[name="${key}"]`);
  67. if (el.is(':checkbox')) {
  68. el.prop('checked', tokenConfig[key]);
  69. } else {
  70. el.val(tokenConfig[key]);
  71. }
  72. el.trigger('change');
  73. }
  74. }
  75. // *************
  76. // consider moving html injection to:
  77. // _replaceHTML | _injectHTML
  78. async activateListeners(html) {
  79. await super.activateListeners(html);
  80. // Disable image path controls
  81. $(html).find('.token-variants-image-select-button').prop('disabled', true);
  82. $(html).find('.file-picker').prop('disabled', true);
  83. $(html).find('.image').prop('disabled', true);
  84. // Remove 'Assign Token' button
  85. $(html).find('.assign-token').remove();
  86. // Add checkboxes to control inclusion of specific tabs in the custom config
  87. const tokenConfig = this.config || getTokenConfig(this.imgSrc, this.imgName);
  88. this.tv_script = tokenConfig.tv_script;
  89. $(html).on('change', '.tva-config-checkbox', this._onCheckboxChange);
  90. const processFormGroup = function (formGroup) {
  91. // Checkbox is not added for the Image Path group
  92. if (!$(formGroup).find('[name="img"]').length) {
  93. let savedField = false;
  94. if (tokenConfig) {
  95. const flatConfig = flattenObject(tokenConfig);
  96. $(formGroup)
  97. .find('[name]')
  98. .each(function (_) {
  99. const name = $(this).attr('name');
  100. if (name in flatConfig) {
  101. savedField = true;
  102. }
  103. });
  104. }
  105. const checkbox = $(
  106. `<div class="tva-config-checkbox"><input type="checkbox" data-dtype="Boolean" ${
  107. savedField ? 'checked=""' : ''
  108. }></div>`
  109. );
  110. if ($(formGroup).find('p.hint').length) {
  111. $(formGroup).find('p.hint').before(checkbox);
  112. } else {
  113. $(formGroup).append(checkbox);
  114. }
  115. checkbox.find('input').trigger('change');
  116. }
  117. };
  118. // Add checkboxes to each form-group to control highlighting and which fields will are to be saved
  119. $(html)
  120. .find('.form-group')
  121. .each(function (index) {
  122. processFormGroup(this);
  123. });
  124. // Add 'update' and 'remove' config buttons
  125. $(html).find('.sheet-footer > button').remove();
  126. $(html)
  127. .find('.sheet-footer')
  128. .append('<button type="submit" value="1"><i class="far fa-save"></i> Save Config</button>');
  129. if (tokenConfig) {
  130. $(html)
  131. .find('.sheet-footer')
  132. .append('<button type="button" class="remove-config"><i class="fas fa-trash"></i> Remove Config</button>');
  133. html.find('.remove-config').click(this._onRemoveConfig.bind(this));
  134. }
  135. // Pre-select image or appearance tab
  136. $(html).find('.tabs > .item[data-tab="image"] > i').trigger('click');
  137. $(html).find('.tabs > .item[data-tab="appearance"] > i').trigger('click');
  138. document.activeElement.blur(); // Hack fix for key UP/DOWN effects not registering after config has been opened
  139. // TokenConfig might be changed by some modules after activateListeners is processed
  140. // Look out for these updates and add checkboxes for any newly added form-groups
  141. const mutate = (mutations) => {
  142. mutations.forEach((mutation) => {
  143. mutation.addedNodes.forEach((node) => {
  144. if (node.nodeName === 'DIV' && node.className === 'form-group') {
  145. processFormGroup(node);
  146. this.applyCustomConfig();
  147. }
  148. });
  149. });
  150. };
  151. const observer = new MutationObserver(mutate);
  152. observer.observe(html[0], {
  153. characterData: false,
  154. attributes: false,
  155. childList: true,
  156. subtree: true,
  157. });
  158. // On any field being changed we want to automatically select the form-group to be included in the update
  159. $(html).on('change', 'input, select', onInputChange);
  160. $(html).on('click', 'button', onInputChange);
  161. this.applyCustomConfig();
  162. }
  163. async _onCheckboxChange(event) {
  164. const checkbox = $(event.target);
  165. checkbox.closest('.form-group').css({
  166. 'outline-color': checkbox.is(':checked') ? 'green' : '#ffcc6e',
  167. 'outline-width': '2px',
  168. 'outline-style': 'dotted',
  169. 'margin-bottom': '5px',
  170. });
  171. checkbox.closest('.tva-config-checkbox').css({
  172. 'outline-color': checkbox.is(':checked') ? 'green' : '#ffcc6e',
  173. 'outline-width': '2px',
  174. 'outline-style': 'solid',
  175. });
  176. }
  177. async _onRemoveConfig(event) {
  178. if (this.config) {
  179. if (this.callback) this.callback({});
  180. } else {
  181. const saved = setTokenConfig(this.imgSrc, this.imgName, null);
  182. if (this.callback) this.callback(saved);
  183. }
  184. this.close();
  185. }
  186. get id() {
  187. return `token-custom-config-${this.object.id}`;
  188. }
  189. _getHeaderButtons() {
  190. const buttons = super._getHeaderButtons();
  191. return buttons;
  192. }
  193. }
  194. // Toggle checkbox if input has been detected inside it's form-group
  195. async function onInputChange(event) {
  196. if (event.target.parentNode.className === 'tva-config-checkbox') return;
  197. $(event.target).closest('.form-group').find('.tva-config-checkbox input').prop('checked', true);
  198. }