intelligent-system-design-language 0.3.21 → 0.3.23

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 (116) hide show
  1. package/.claude/agents/langium-language-designer.md +38 -38
  2. package/.claude/agents/typescript-vscode-expert.md +29 -29
  3. package/.claude/agents/ui-ux-designer.md +36 -36
  4. package/.claude/settings.local.json +33 -33
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -6
  6. package/.idea/isdl.iml +13 -13
  7. package/.idea/modules.xml +8 -8
  8. package/.idea/vcs.xml +6 -6
  9. package/.idea/watcherTasks.xml +3 -3
  10. package/.vscodeignore +18 -18
  11. package/LICENSE +673 -673
  12. package/README.md +86 -86
  13. package/bin/cli.js +4 -4
  14. package/bin/lsp.js +8 -8
  15. package/out/_backgrounds.scss +91 -91
  16. package/out/_handlebars.scss +497 -497
  17. package/out/_isdlStyles.scss +1444 -1381
  18. package/out/_vuetifyOverrides.scss +425 -425
  19. package/out/_vuetifyStyles.scss +31957 -31957
  20. package/out/cli/components/_backgrounds.scss +91 -91
  21. package/out/cli/components/_handlebars.scss +497 -497
  22. package/out/cli/components/_isdlStyles.scss +1444 -1381
  23. package/out/cli/components/_vuetifyOverrides.scss +425 -425
  24. package/out/cli/components/_vuetifyStyles.scss +31957 -31957
  25. package/out/cli/components/active-effect-sheet-generator.js +453 -453
  26. package/out/cli/components/chat-card-generator.js +654 -651
  27. package/out/cli/components/chat-card-generator.js.map +1 -1
  28. package/out/cli/components/css-generator.js +4 -4
  29. package/out/cli/components/damage-roll-generator.js +160 -160
  30. package/out/cli/components/datamodel-generator.js +264 -257
  31. package/out/cli/components/datamodel-generator.js.map +1 -1
  32. package/out/cli/components/derived-data-generator.js +923 -923
  33. package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
  34. package/out/cli/components/init-hook-generator.js +495 -495
  35. package/out/cli/components/language-generator.js +1 -1
  36. package/out/cli/components/language-generator.js.map +1 -1
  37. package/out/cli/components/measured-template-preview.js +221 -221
  38. package/out/cli/components/method-generator.js +979 -887
  39. package/out/cli/components/method-generator.js.map +1 -1
  40. package/out/cli/components/ready-hook-generator.js +404 -404
  41. package/out/cli/components/token-generator.js +116 -116
  42. package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
  43. package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
  44. package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
  45. package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
  46. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
  47. package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
  48. package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
  49. package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
  50. package/out/cli/components/vue/base-components/vue-dice.js +98 -98
  51. package/out/cli/components/vue/base-components/vue-die.js +73 -73
  52. package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
  53. package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
  54. package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
  55. package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
  56. package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
  57. package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
  58. package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
  59. package/out/cli/components/vue/base-components/vue-money.js +483 -483
  60. package/out/cli/components/vue/base-components/vue-number.js +174 -174
  61. package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
  62. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
  63. package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
  64. package/out/cli/components/vue/base-components/vue-resource.js +136 -136
  65. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +286 -109
  66. package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -1
  67. package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
  68. package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
  69. package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
  70. package/out/cli/components/vue/base-components/vue-string.js +60 -60
  71. package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
  72. package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
  73. package/out/cli/components/vue/vue-action-component-generator.js +64 -64
  74. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
  75. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
  76. package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
  77. package/out/cli/components/vue/vue-document-creation-app.js +121 -121
  78. package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
  79. package/out/cli/components/vue/vue-generator.js +40 -40
  80. package/out/cli/components/vue/vue-mixin.js +296 -296
  81. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
  82. package/out/cli/components/vue/vue-prompt-generator.js +91 -76
  83. package/out/cli/components/vue/vue-prompt-generator.js.map +1 -1
  84. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
  85. package/out/cli/components/vue/vue-sheet-application-generator.js +1177 -1167
  86. package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -1
  87. package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
  88. package/out/cli/generator.js +438 -433
  89. package/out/cli/generator.js.map +1 -1
  90. package/out/extension/github/githubAuthProvider.js +71 -29
  91. package/out/extension/github/githubAuthProvider.js.map +1 -1
  92. package/out/extension/github/githubGistManager.js +4 -3
  93. package/out/extension/github/githubGistManager.js.map +1 -1
  94. package/out/extension/github/githubManager.js +40 -38
  95. package/out/extension/github/githubManager.js.map +1 -1
  96. package/out/extension/github/githubQuickActions.js +120 -120
  97. package/out/extension/github/system-workflow.yml +47 -47
  98. package/out/extension/main.cjs +909 -532
  99. package/out/extension/main.cjs.map +3 -3
  100. package/out/extension/package.json +419 -419
  101. package/out/language/generated/ast.js +51 -2
  102. package/out/language/generated/ast.js.map +1 -1
  103. package/out/language/generated/grammar.js +14240 -13991
  104. package/out/language/generated/grammar.js.map +1 -1
  105. package/out/language/intelligent-system-design-language-validator.js +32 -2
  106. package/out/language/intelligent-system-design-language-validator.js.map +1 -1
  107. package/out/language/isdl-scope-provider.js +14 -1
  108. package/out/language/isdl-scope-provider.js.map +1 -1
  109. package/out/language/main.cjs +913 -569
  110. package/out/language/main.cjs.map +3 -3
  111. package/out/package.json +419 -419
  112. package/out/progressbar.min.js +6 -6
  113. package/out/styles.scss +762 -747
  114. package/out/test/validating/diagnostics.test.js +40 -0
  115. package/out/test/validating/diagnostics.test.js.map +1 -1
  116. package/package.json +419 -419
@@ -33,469 +33,469 @@ export function generateDocumentVueSheet(entry, id, document, destination) {
33
33
  const functionName = toMachineIdentifier(functionDef.name);
34
34
  console.log(`Generating function ${functionName} for ${document.name} Vue sheet.`);
35
35
  if (functionDef.params.length > 0) {
36
- return expandToNode `
37
- async function_${functionName}(context, update, embeddedUpdate, parentUpdate, parentEmbeddedUpdate, targetUpdate, targetEmbeddedUpdate, ${joinToNode(functionDef.params, param => expandToNode `${param.param.name}`, { separator: ', ' })}) {
38
- let system = context.object.system;
39
- ${translateBodyExpressionToJavascript(entry, id, functionDef.method.body, false, functionDef)}
40
- }
36
+ return expandToNode `
37
+ async function_${functionName}(context, update, embeddedUpdate, parentUpdate, parentEmbeddedUpdate, targetUpdate, targetEmbeddedUpdate, ${joinToNode(functionDef.params, param => expandToNode `${param.param.name}`, { separator: ', ' })}) {
38
+ let system = context.object.system;
39
+ ${translateBodyExpressionToJavascript(entry, id, functionDef.method.body, false, functionDef)}
40
+ }
41
41
  `.appendNewLine();
42
42
  }
43
43
  // No-arg functions take the caller's `context` as their first argument too (the call site
44
44
  // passes context regardless of arity). Deriving `system` from it — mirroring the param
45
45
  // branch — keeps `self.*` (which compiles to `system.*`) and `context.object` both valid.
46
- return expandToNode `
47
- async function_${functionName}(context, update, embeddedUpdate, parentUpdate, parentEmbeddedUpdate, targetUpdate, targetEmbeddedUpdate) {
48
- let system = context.object.system;
49
- ${translateBodyExpressionToJavascript(entry, id, functionDef.method.body, false, functionDef)}
50
- }
46
+ return expandToNode `
47
+ async function_${functionName}(context, update, embeddedUpdate, parentUpdate, parentEmbeddedUpdate, targetUpdate, targetEmbeddedUpdate) {
48
+ let system = context.object.system;
49
+ ${translateBodyExpressionToJavascript(entry, id, functionDef.method.body, false, functionDef)}
50
+ }
51
51
  `.appendNewLine();
52
52
  }
53
- const fileNode = expandToNode `
54
- import VueRenderingMixin from '../VueRenderingMixin.mjs';
55
- import { ${document.name}${titleize(type)}App } from "../components/components.vue.es.mjs";
56
- import ${entry.config.name}Roll from "../../../rolls/roll.mjs";
57
- import ${entry.config.name}DamageRoll from "../../../rolls/damage-roll.mjs";
58
- const { DOCUMENT_OWNERSHIP_LEVELS } = CONST;
59
-
60
- export default class ${document.name}VueSheet extends VueRenderingMixin(foundry.applications.sheets.${titleize(type)}SheetV2) {
61
-
62
- constructor(options = {}) {
63
- super(options);
64
- this.#dragDrop = this.#createDragDropHandlers();
65
- }
66
-
67
- vueParts = {
68
- "${vueComponentName}": {
69
- component: ${document.name}${titleize(type)}App,
70
- template: "<${vueComponentName} :context=\\"context\\">Vue rendering for sheet failed.</${vueComponentName}>"
71
- }
72
- };
73
-
74
- _arrayEntryKey = 0;
75
- _renderKey = 0;
76
-
77
- /** @override */
78
- static DEFAULT_OPTIONS = {
79
- classes: ["${id}", "sheet", "vue-sheet", "${type}", "${document.name.toLowerCase()}-sheet"],
80
- viewPermission: DOCUMENT_OWNERSHIP_LEVELS.LIMITED,
81
- editPermission: DOCUMENT_OWNERSHIP_LEVELS.OWNER,
82
- position: {
83
- width: ${sheetWidth},
84
- height: ${sheetHeight},
85
- },
86
- window: {
87
- resizable: true,
88
- title: "${humanize(document.name)}",
89
- controls: ${type == "item" ? expandToNode `[]` : expandToNode `[
90
- {
91
- action: "configurePrototypeToken",
92
- icon: "fa-solid fa-user-circle",
93
- label: "TOKEN.TitlePrototype",
94
- ownership: "OWNER"
95
- },
96
- {
97
- action: "showPortraitArtwork",
98
- icon: "fa-solid fa-image",
99
- label: "SIDEBAR.CharArt",
100
- ownership: "OWNER"
101
- },
102
- {
103
- action: "showTokenArtwork",
104
- icon: "fa-solid fa-image",
105
- label: "SIDEBAR.TokenArt",
106
- ownership: "OWNER"
107
- }
108
- ]`}
109
- },
110
- tag: "form",
111
- actions: {
112
- onEditImage: this._onEditImage
113
- },
114
- changeActions: {
115
- },
116
- // Custom property that's merged into this.options
117
- dragDrop: [
118
- {dragSelector: "tr", dropSelector: ".tabs-container, .datatable-drop-zone"},
119
- {dropSelector: ".single-document"},
120
- {dragSelector: ".paper-doll-slot", dropSelector: ".paper-doll-slot"},
121
- {dragSelector: ".inventory-slot.filled", dropSelector: ".inventory-grid-container"}
122
- ],
123
- form: {
124
- submitOnChange: true,
125
- submitOnClose: true,
126
- closeOnSubmit: false,
127
- }
128
- };
129
-
130
- async _prepareContext(options) {
131
- // Output initialization
132
- const context = {
133
- // Validates both permissions and compendium status
134
- editable: this.isEditable,
135
- owner: this.document.isOwner,
136
- limited: this.document.limited,
137
-
138
- // Add the document.
139
- object: this.document.toObject(),
140
- document: this.document,
141
-
142
- // Add the data to context.data for easier access, as well as flags.
143
- system: this.document.system,
144
- flags: this.document.flags,
145
-
146
- // Roll data.
147
- rollData: this.document.getRollData() ?? {},
148
-
149
- // Editors
150
- editors: {},
151
-
152
- // Force re-renders. Defined in the vue mixin.
153
- _renderKey: this._renderKey ?? 0,
154
- _arrayEntryKey: this._arrayEntryKey ?? 0,
155
- // tabs: this._getTabs(options.parts),
156
-
157
- // Necessary for formInput and formFields helpers
158
- fields: this.document.schema.fields,
159
- systemFields: this.document.system.schema.fields
160
- };
161
-
162
- // Enrich editors
163
- await this._enrichEditor(context, "description");
164
- ${joinToNode(htmlElements, htmlElement => expandToNode `await this._enrichEditor(context, "${htmlElement.name.toLowerCase()}");`, { appendNewLineIfNotEmpty: true })}
165
-
166
- // Make another pass through the editors to fix the element contents.
167
- for (let [field, editor] of Object.entries(context.editors)) {
168
- if (context.editors[field].element) {
169
- context.editors[field].element.innerHTML = context.editors[field].enriched;
170
- }
171
- }
172
-
173
- return context;
174
- }
175
-
176
- async _enrichEditor(context, field) {
177
- const enrichmentOptions = {
178
- // Whether to show secret blocks in the finished html
179
- secrets: this.document.isOwner,
180
- // Data to fill in for inline rolls
181
- rollData: this.document.getRollData() ?? {},
182
- // Relative UUID resolution
183
- relativeTo: this.document
184
- };
185
-
186
- const editorOptions = {
187
- toggled: true,
188
- collaborate: true,
189
- documentUUID: this.document.uuid,
190
- height: 300
191
- };
192
-
193
- const editorValue = this.document.system?.[field] ?? foundry.utils.getProperty(this.document.system, field);
194
- context.editors[\`system.\${field}\`] = {
195
- enriched: await TextEditor.enrichHTML(editorValue, enrichmentOptions),
196
- element: foundry.applications.elements.HTMLProseMirrorElement.create({
197
- ...editorOptions,
198
- name: \`system.\${field}\`,
199
- value: editorValue ?? ""
200
- })
201
- };
202
- }
203
-
204
- /**
205
- * Actions performed after any render of the Application.
206
- * Post-render steps are not awaited by the render process.
207
- * @param {ApplicationRenderContext} context Prepared context data
208
- * @param {RenderOptions} options Provided render options
209
- * @protected
210
- */
211
- _onRender(context, options) {
212
- this.#dragDrop.forEach((d) => d.bind(this.element));
213
- // You may want to add other special handling here
214
- // Foundry comes with a large number of utility classes, e.g. SearchFilter
215
- // That you may want to implement yourself.
216
- }
217
-
218
- /**
219
- * Handle changing a Document's image.
220
- *
221
- * @this ArchmageBaseItemSheetV2
222
- * @param {PointerEvent} event The originating click event
223
- * @param {HTMLElement} target The capturing HTML element which defined a [data-action]
224
- * @returns {Promise}
225
- * @protected
226
- */
227
- static async _onEditImage(event, target) {
228
- if (!this.isEditable) return false;
229
- const attr = target.dataset.edit;
230
- const current = foundry.utils.getProperty(this.document, attr);
231
- const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
232
- const fp = new FilePicker({
233
- current,
234
- type: "image",
235
- redirectToRoot: img ? [img] : [],
236
- callback: (path) => {
237
- target.src = path;
238
- this.document.update({ [attr]: path });
239
- this._renderKey++;
240
- },
241
- top: this.position.top + 40,
242
- left: this.position.left + 10
243
- });
244
- return fp.browse();
245
- }
246
-
247
- _prepareSubmitData(event, form, formData) {
248
- // We don't modify the image via the sheet itself, so we can remove it from the submit data to avoid errors.
249
- delete formData.object.img;
250
- return super._prepareSubmitData(event, form, formData);
251
- }
252
-
253
- // Drag and Drop
254
-
255
- /**
256
- * Returns an array of DragDrop instances
257
- * @type {DragDrop[]}
258
- */
259
- get dragDrop() {
260
- return this.#dragDrop;
261
- }
262
-
263
- /**
264
- * Define whether a user is able to begin a dragstart workflow for a given drag selector
265
- * @param {string} selector The candidate HTML selector for dragging
266
- * @returns {boolean} Can the current user drag this selector?
267
- * @protected
268
- */
269
- _canDragStart(selector) {
270
- return this.isEditable;
271
- }
272
-
273
- /**
274
- * Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
275
- * @param {string} selector The candidate HTML selector for the drop target
276
- * @returns {boolean} Can the current user drop on this selector?
277
- * @protected
278
- */
279
- _canDragDrop(selector) {
280
- return this.isEditable;
281
- }
282
-
283
- /**
284
- * Callback actions which occur at the beginning of a drag start workflow.
285
- * @param {DragEvent} event The originating DragEvent
286
- * @protected
287
- */
288
- _onDragStart(event) {
289
- if (event.currentTarget.classList.contains("paper-doll-slot")) {
290
- // Remove the item from the slot
291
- const name = event.currentTarget.dataset.name;
292
- const update = {};
293
- update[name] = null;
294
- this.document.update(update);
295
- }
296
- else if (event.currentTarget.classList.contains("inventory-slot")) {
297
- // Inventory slots - data is set by Vue component's @dragstart handler
298
- // Nothing to do here, the Vue component handles it
299
- return;
300
- }
301
- else {
302
- // For table rows, currentTarget should already be the tr element
303
- const tr = event.currentTarget.tagName === "TR" ? event.currentTarget : event.currentTarget.closest("tr");
304
- if (tr) {
305
- // Try to get UUID from data attributes
306
- let uuid = tr.dataset.uuid;
307
- let type = tr.dataset.type;
308
-
309
- // If not found, try to get from item map (for Vuetify tables)
310
- if (!uuid && tr.dataset.itemId && tr.dataset.documentId) {
311
- const itemMap = window.isdlItemMaps?.get(tr.dataset.documentId);
312
- if (itemMap) {
313
- const item = itemMap.value.get(tr.dataset.itemId);
314
- if (item) {
315
- uuid = item.uuid;
316
- type = item.type || 'Item';
317
- }
318
- }
319
- }
320
-
321
- if (!uuid) {
322
- console.error("No UUID found on tr element", tr);
323
- return;
324
- }
325
-
326
- const data = {
327
- type: type == "ActiveEffect" ? "ActiveEffect" : "Item",
328
- uuid: uuid
329
- };
330
-
331
- event.dataTransfer.setData("text/plain", JSON.stringify(data));
332
- }
333
- }
334
- }
335
-
336
- /**
337
- * Callback actions which occur when a dragged element is over a drop target.
338
- * @param {DragEvent} event The originating DragEvent
339
- * @protected
340
- */
341
- _onDragOver(event) {}
342
-
343
- /* -------------------------------------------- */
344
-
345
- async _onDrop(event) {
346
- const data = JSON.parse(event.dataTransfer.getData("text/plain"));
347
-
348
- // If the drop target is a single document, handle it differently
349
- const linkedClasses = [ "single-document", "paper-doll-slot" ];
350
- const eventClasses = Array.from(event.currentTarget.classList);
351
- if (eventClasses.find(c => linkedClasses.includes(c))) {
352
- const doc = await fromUuid(data.uuid);
353
- if ( !doc ) return;
354
- if ( doc.type !== event.currentTarget.dataset.type ) {
355
- ui.notifications.error(\`Expected a \${event.currentTarget.dataset.type} type Document, but got a \${doc.type} type one instead. \`);
356
- return;
357
- }
358
-
359
- const update = {};
360
- update[event.currentTarget.dataset.name] = data.uuid;
361
- await this.document.update(update);
362
- return;
363
- }
364
-
365
- const dropTypes = ["Item", "ActiveEffect"];
366
- if ( !dropTypes.includes(data.type) ) return;
367
- const item = await fromUuid(data.uuid);
368
- if ( !item ) return;
369
-
370
- // Prevent duplicates when dropping an item that's already owned by this document
371
- if ( item.parent?.uuid === this.document.uuid ) return;
372
-
373
- if ( data.type === "ActiveEffect" ) {
374
- ActiveEffect.createDocuments([item], {parent: this.document})
375
- return;
376
- }
377
-
378
- Item.createDocuments([item], {parent: this.document})
379
- }
380
-
381
- /* -------------------------------------------- */
382
-
383
- /**
384
- * Returns an array of DragDrop instances
385
- * @type {DragDrop[]}
386
- */
387
- get dragDrop() {
388
- return this.#dragDrop;
389
- }
390
-
391
- // This is marked as private because there's no real need
392
- // for subclasses or external hooks to mess with it directly
393
- #dragDrop;
394
-
395
- /**
396
- * Create drag-and-drop workflow handlers for this Application
397
- * @returns {DragDrop[]} An array of DragDrop handlers
398
- * @private
399
- */
400
- #createDragDropHandlers() {
401
- return this.options.dragDrop.map((d) => {
402
- d.permissions = {
403
- dragstart: this._canDragStart.bind(this),
404
- drop: this._canDragDrop.bind(this)
405
- };
406
- d.callbacks = {
407
- dragstart: this._onDragStart.bind(this),
408
- dragover: this._onDragOver.bind(this),
409
- drop: this._onDrop.bind(this)
410
- };
411
- return new DragDrop(d);
412
- });
413
- }
414
-
415
- /* -------------------------------------------- */
416
-
417
- async _onAction(event) {
418
- if (event.preventDefault) event.preventDefault();
419
- const action = event.currentTarget.dataset.action;
420
- switch ( action ) {
421
- ${joinToNode(actions, property => `case "${property.name.toLowerCase()}": this._on${property.name}Action(event, this.document.system); break;`, { appendNewLineIfNotEmpty: true })}
422
- }
423
- }
424
-
425
- ${joinToNode(actions, property => generateAction(property), { appendNewLineIfNotEmpty: true })}
426
-
427
- /* -------------------------------------------- */
428
-
429
- // Attribute click handlers (function: param)
430
- ${joinToNode(attributeFunctions, generateAttributeFunctionMethod, { appendNewLineIfNotEmpty: true })}
431
-
432
- /* -------------------------------------------- */
433
-
434
- // User defined methods
435
- ${joinToNode(functions, generateFunctionDefinition, { appendNewLineIfNotEmpty: true })}
436
- }
53
+ const fileNode = expandToNode `
54
+ import VueRenderingMixin from '../VueRenderingMixin.mjs';
55
+ import { ${document.name}${titleize(type)}App } from "../components/components.vue.es.mjs";
56
+ import ${entry.config.name}Roll from "../../../rolls/roll.mjs";
57
+ import ${entry.config.name}DamageRoll from "../../../rolls/damage-roll.mjs";
58
+ const { DOCUMENT_OWNERSHIP_LEVELS } = CONST;
59
+
60
+ export default class ${document.name}VueSheet extends VueRenderingMixin(foundry.applications.sheets.${titleize(type)}SheetV2) {
61
+
62
+ constructor(options = {}) {
63
+ super(options);
64
+ this.#dragDrop = this.#createDragDropHandlers();
65
+ }
66
+
67
+ vueParts = {
68
+ "${vueComponentName}": {
69
+ component: ${document.name}${titleize(type)}App,
70
+ template: "<${vueComponentName} :context=\\"context\\">Vue rendering for sheet failed.</${vueComponentName}>"
71
+ }
72
+ };
73
+
74
+ _arrayEntryKey = 0;
75
+ _renderKey = 0;
76
+
77
+ /** @override */
78
+ static DEFAULT_OPTIONS = {
79
+ classes: ["${id}", "sheet", "vue-sheet", "${type}", "${document.name.toLowerCase()}-sheet"],
80
+ viewPermission: DOCUMENT_OWNERSHIP_LEVELS.LIMITED,
81
+ editPermission: DOCUMENT_OWNERSHIP_LEVELS.OWNER,
82
+ position: {
83
+ width: ${sheetWidth},
84
+ height: ${sheetHeight},
85
+ },
86
+ window: {
87
+ resizable: true,
88
+ title: "${humanize(document.name)}",
89
+ controls: ${type == "item" ? expandToNode `[]` : expandToNode `[
90
+ {
91
+ action: "configurePrototypeToken",
92
+ icon: "fa-solid fa-user-circle",
93
+ label: "TOKEN.TitlePrototype",
94
+ ownership: "OWNER"
95
+ },
96
+ {
97
+ action: "showPortraitArtwork",
98
+ icon: "fa-solid fa-image",
99
+ label: "SIDEBAR.CharArt",
100
+ ownership: "OWNER"
101
+ },
102
+ {
103
+ action: "showTokenArtwork",
104
+ icon: "fa-solid fa-image",
105
+ label: "SIDEBAR.TokenArt",
106
+ ownership: "OWNER"
107
+ }
108
+ ]`}
109
+ },
110
+ tag: "form",
111
+ actions: {
112
+ onEditImage: this._onEditImage
113
+ },
114
+ changeActions: {
115
+ },
116
+ // Custom property that's merged into this.options
117
+ dragDrop: [
118
+ {dragSelector: "tr", dropSelector: ".tabs-container, .datatable-drop-zone"},
119
+ {dropSelector: ".single-document"},
120
+ {dragSelector: ".paper-doll-slot", dropSelector: ".paper-doll-slot"},
121
+ {dragSelector: ".inventory-slot.filled", dropSelector: ".inventory-grid-container"}
122
+ ],
123
+ form: {
124
+ submitOnChange: true,
125
+ submitOnClose: true,
126
+ closeOnSubmit: false,
127
+ }
128
+ };
129
+
130
+ async _prepareContext(options) {
131
+ // Output initialization
132
+ const context = {
133
+ // Validates both permissions and compendium status
134
+ editable: this.isEditable,
135
+ owner: this.document.isOwner,
136
+ limited: this.document.limited,
137
+
138
+ // Add the document.
139
+ object: this.document.toObject(),
140
+ document: this.document,
141
+
142
+ // Add the data to context.data for easier access, as well as flags.
143
+ system: this.document.system,
144
+ flags: this.document.flags,
145
+
146
+ // Roll data.
147
+ rollData: this.document.getRollData() ?? {},
148
+
149
+ // Editors
150
+ editors: {},
151
+
152
+ // Force re-renders. Defined in the vue mixin.
153
+ _renderKey: this._renderKey ?? 0,
154
+ _arrayEntryKey: this._arrayEntryKey ?? 0,
155
+ // tabs: this._getTabs(options.parts),
156
+
157
+ // Necessary for formInput and formFields helpers
158
+ fields: this.document.schema.fields,
159
+ systemFields: this.document.system.schema.fields
160
+ };
161
+
162
+ // Enrich editors
163
+ await this._enrichEditor(context, "description");
164
+ ${joinToNode(htmlElements, htmlElement => expandToNode `await this._enrichEditor(context, "${htmlElement.name.toLowerCase()}");`, { appendNewLineIfNotEmpty: true })}
165
+
166
+ // Make another pass through the editors to fix the element contents.
167
+ for (let [field, editor] of Object.entries(context.editors)) {
168
+ if (context.editors[field].element) {
169
+ context.editors[field].element.innerHTML = context.editors[field].enriched;
170
+ }
171
+ }
172
+
173
+ return context;
174
+ }
175
+
176
+ async _enrichEditor(context, field) {
177
+ const enrichmentOptions = {
178
+ // Whether to show secret blocks in the finished html
179
+ secrets: this.document.isOwner,
180
+ // Data to fill in for inline rolls
181
+ rollData: this.document.getRollData() ?? {},
182
+ // Relative UUID resolution
183
+ relativeTo: this.document
184
+ };
185
+
186
+ const editorOptions = {
187
+ toggled: true,
188
+ collaborate: true,
189
+ documentUUID: this.document.uuid,
190
+ height: 300
191
+ };
192
+
193
+ const editorValue = this.document.system?.[field] ?? foundry.utils.getProperty(this.document.system, field);
194
+ context.editors[\`system.\${field}\`] = {
195
+ enriched: await TextEditor.enrichHTML(editorValue, enrichmentOptions),
196
+ element: foundry.applications.elements.HTMLProseMirrorElement.create({
197
+ ...editorOptions,
198
+ name: \`system.\${field}\`,
199
+ value: editorValue ?? ""
200
+ })
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Actions performed after any render of the Application.
206
+ * Post-render steps are not awaited by the render process.
207
+ * @param {ApplicationRenderContext} context Prepared context data
208
+ * @param {RenderOptions} options Provided render options
209
+ * @protected
210
+ */
211
+ _onRender(context, options) {
212
+ this.#dragDrop.forEach((d) => d.bind(this.element));
213
+ // You may want to add other special handling here
214
+ // Foundry comes with a large number of utility classes, e.g. SearchFilter
215
+ // That you may want to implement yourself.
216
+ }
217
+
218
+ /**
219
+ * Handle changing a Document's image.
220
+ *
221
+ * @this ArchmageBaseItemSheetV2
222
+ * @param {PointerEvent} event The originating click event
223
+ * @param {HTMLElement} target The capturing HTML element which defined a [data-action]
224
+ * @returns {Promise}
225
+ * @protected
226
+ */
227
+ static async _onEditImage(event, target) {
228
+ if (!this.isEditable) return false;
229
+ const attr = target.dataset.edit;
230
+ const current = foundry.utils.getProperty(this.document, attr);
231
+ const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {};
232
+ const fp = new FilePicker({
233
+ current,
234
+ type: "image",
235
+ redirectToRoot: img ? [img] : [],
236
+ callback: (path) => {
237
+ target.src = path;
238
+ this.document.update({ [attr]: path });
239
+ this._renderKey++;
240
+ },
241
+ top: this.position.top + 40,
242
+ left: this.position.left + 10
243
+ });
244
+ return fp.browse();
245
+ }
246
+
247
+ _prepareSubmitData(event, form, formData) {
248
+ // We don't modify the image via the sheet itself, so we can remove it from the submit data to avoid errors.
249
+ delete formData.object.img;
250
+ return super._prepareSubmitData(event, form, formData);
251
+ }
252
+
253
+ // Drag and Drop
254
+
255
+ /**
256
+ * Returns an array of DragDrop instances
257
+ * @type {DragDrop[]}
258
+ */
259
+ get dragDrop() {
260
+ return this.#dragDrop;
261
+ }
262
+
263
+ /**
264
+ * Define whether a user is able to begin a dragstart workflow for a given drag selector
265
+ * @param {string} selector The candidate HTML selector for dragging
266
+ * @returns {boolean} Can the current user drag this selector?
267
+ * @protected
268
+ */
269
+ _canDragStart(selector) {
270
+ return this.isEditable;
271
+ }
272
+
273
+ /**
274
+ * Define whether a user is able to conclude a drag-and-drop workflow for a given drop selector
275
+ * @param {string} selector The candidate HTML selector for the drop target
276
+ * @returns {boolean} Can the current user drop on this selector?
277
+ * @protected
278
+ */
279
+ _canDragDrop(selector) {
280
+ return this.isEditable;
281
+ }
282
+
283
+ /**
284
+ * Callback actions which occur at the beginning of a drag start workflow.
285
+ * @param {DragEvent} event The originating DragEvent
286
+ * @protected
287
+ */
288
+ _onDragStart(event) {
289
+ if (event.currentTarget.classList.contains("paper-doll-slot")) {
290
+ // Remove the item from the slot
291
+ const name = event.currentTarget.dataset.name;
292
+ const update = {};
293
+ update[name] = null;
294
+ this.document.update(update);
295
+ }
296
+ else if (event.currentTarget.classList.contains("inventory-slot")) {
297
+ // Inventory slots - data is set by Vue component's @dragstart handler
298
+ // Nothing to do here, the Vue component handles it
299
+ return;
300
+ }
301
+ else {
302
+ // For table rows, currentTarget should already be the tr element
303
+ const tr = event.currentTarget.tagName === "TR" ? event.currentTarget : event.currentTarget.closest("tr");
304
+ if (tr) {
305
+ // Try to get UUID from data attributes
306
+ let uuid = tr.dataset.uuid;
307
+ let type = tr.dataset.type;
308
+
309
+ // If not found, try to get from item map (for Vuetify tables)
310
+ if (!uuid && tr.dataset.itemId && tr.dataset.documentId) {
311
+ const itemMap = window.isdlItemMaps?.get(tr.dataset.documentId);
312
+ if (itemMap) {
313
+ const item = itemMap.value.get(tr.dataset.itemId);
314
+ if (item) {
315
+ uuid = item.uuid;
316
+ type = item.type || 'Item';
317
+ }
318
+ }
319
+ }
320
+
321
+ if (!uuid) {
322
+ console.error("No UUID found on tr element", tr);
323
+ return;
324
+ }
325
+
326
+ const data = {
327
+ type: type == "ActiveEffect" ? "ActiveEffect" : "Item",
328
+ uuid: uuid
329
+ };
330
+
331
+ event.dataTransfer.setData("text/plain", JSON.stringify(data));
332
+ }
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Callback actions which occur when a dragged element is over a drop target.
338
+ * @param {DragEvent} event The originating DragEvent
339
+ * @protected
340
+ */
341
+ _onDragOver(event) {}
342
+
343
+ /* -------------------------------------------- */
344
+
345
+ async _onDrop(event) {
346
+ const data = JSON.parse(event.dataTransfer.getData("text/plain"));
347
+
348
+ // If the drop target is a single document, handle it differently
349
+ const linkedClasses = [ "single-document", "paper-doll-slot" ];
350
+ const eventClasses = Array.from(event.currentTarget.classList);
351
+ if (eventClasses.find(c => linkedClasses.includes(c))) {
352
+ const doc = await fromUuid(data.uuid);
353
+ if ( !doc ) return;
354
+ if ( doc.type !== event.currentTarget.dataset.type ) {
355
+ ui.notifications.error(\`Expected a \${event.currentTarget.dataset.type} type Document, but got a \${doc.type} type one instead. \`);
356
+ return;
357
+ }
358
+
359
+ const update = {};
360
+ update[event.currentTarget.dataset.name] = data.uuid;
361
+ await this.document.update(update);
362
+ return;
363
+ }
364
+
365
+ const dropTypes = ["Item", "ActiveEffect"];
366
+ if ( !dropTypes.includes(data.type) ) return;
367
+ const item = await fromUuid(data.uuid);
368
+ if ( !item ) return;
369
+
370
+ // Prevent duplicates when dropping an item that's already owned by this document
371
+ if ( item.parent?.uuid === this.document.uuid ) return;
372
+
373
+ if ( data.type === "ActiveEffect" ) {
374
+ ActiveEffect.createDocuments([item], {parent: this.document})
375
+ return;
376
+ }
377
+
378
+ Item.createDocuments([item], {parent: this.document})
379
+ }
380
+
381
+ /* -------------------------------------------- */
382
+
383
+ /**
384
+ * Returns an array of DragDrop instances
385
+ * @type {DragDrop[]}
386
+ */
387
+ get dragDrop() {
388
+ return this.#dragDrop;
389
+ }
390
+
391
+ // This is marked as private because there's no real need
392
+ // for subclasses or external hooks to mess with it directly
393
+ #dragDrop;
394
+
395
+ /**
396
+ * Create drag-and-drop workflow handlers for this Application
397
+ * @returns {DragDrop[]} An array of DragDrop handlers
398
+ * @private
399
+ */
400
+ #createDragDropHandlers() {
401
+ return this.options.dragDrop.map((d) => {
402
+ d.permissions = {
403
+ dragstart: this._canDragStart.bind(this),
404
+ drop: this._canDragDrop.bind(this)
405
+ };
406
+ d.callbacks = {
407
+ dragstart: this._onDragStart.bind(this),
408
+ dragover: this._onDragOver.bind(this),
409
+ drop: this._onDrop.bind(this)
410
+ };
411
+ return new DragDrop(d);
412
+ });
413
+ }
414
+
415
+ /* -------------------------------------------- */
416
+
417
+ async _onAction(event) {
418
+ if (event.preventDefault) event.preventDefault();
419
+ const action = event.currentTarget.dataset.action;
420
+ switch ( action ) {
421
+ ${joinToNode(actions, property => `case "${property.name.toLowerCase()}": this._on${property.name}Action(event, this.document.system); break;`, { appendNewLineIfNotEmpty: true })}
422
+ }
423
+ }
424
+
425
+ ${joinToNode(actions, property => generateAction(property), { appendNewLineIfNotEmpty: true })}
426
+
427
+ /* -------------------------------------------- */
428
+
429
+ // Attribute click handlers (function: param)
430
+ ${joinToNode(attributeFunctions, generateAttributeFunctionMethod, { appendNewLineIfNotEmpty: true })}
431
+
432
+ /* -------------------------------------------- */
433
+
434
+ // User defined methods
435
+ ${joinToNode(functions, generateFunctionDefinition, { appendNewLineIfNotEmpty: true })}
436
+ }
437
437
  `.appendNewLineIfNotEmpty();
438
438
  fs.writeFileSync(generatedFilePath, toString(fileNode));
439
439
  function generateAction(action) {
440
440
  console.log(`Generating action ${action.name} for ${document.name} Vue sheet.`);
441
- return expandToNode `
442
-
443
- /* -------------------------------------------- */
444
-
445
- async _on${action.name}Action(event, system) {
446
- if (event.preventDefault) event.preventDefault();
447
- let update = {};
448
- let embeddedUpdate = {};
449
- let parentUpdate = {};
450
- let parentEmbeddedUpdate = {};
451
- let targetUpdate = {};
452
- let targetEmbeddedUpdate = {};
453
- let selfDeleted = false;
454
- let rerender = false;
455
- let document = this.document;
456
- const context = {
457
- object: this.document,
458
- target: game.user.getTargetOrNothing()
459
- };
460
- // If this is an item, attach the parent
461
- if (document.documentName === "Item" && document.parent) {
462
- context.actor = document.parent;
463
- }
464
- else {
465
- context.actor = document;
466
- }
467
- ${translateExpression(entry, id, action.method, false, action)}
468
- if (!selfDeleted && Object.keys(update).length > 0) {
469
- await this.document.update(update);
470
- rerender = true;
471
- }
472
- if (!selfDeleted && Object.keys(embeddedUpdate).length > 0) {
473
- for (let key of Object.keys(embeddedUpdate)) {
474
- await this.document.updateEmbeddedDocuments("Item", embeddedUpdate[key]);
475
- }
476
- rerender = true;
477
- }
478
- if (Object.keys(parentUpdate).length > 0) {
479
- await this.document.parent.update(parentUpdate);
480
- rerender = true;
481
- }
482
- if (Object.keys(parentEmbeddedUpdate).length > 0) {
483
- for (let key of Object.keys(parentEmbeddedUpdate)) {
484
- await document.parent.updateEmbeddedDocuments("Item", parentEmbeddedUpdate[key]);
485
- }
486
- }
487
- if (Object.keys(targetUpdate).length > 0) {
488
- await context.target.update(targetUpdate);
489
- }
490
- if (Object.keys(targetEmbeddedUpdate).length > 0) {
491
- for (let key of Object.keys(targetEmbeddedUpdate)) {
492
- await context.target.updateEmbeddedDocuments("Item", targetEmbeddedUpdate[key]);
493
- }
494
- }
495
- if (rerender) {
496
- this.render();
497
- }
498
- }
441
+ return expandToNode `
442
+
443
+ /* -------------------------------------------- */
444
+
445
+ async _on${action.name}Action(event, system) {
446
+ if (event.preventDefault) event.preventDefault();
447
+ let update = {};
448
+ let embeddedUpdate = {};
449
+ let parentUpdate = {};
450
+ let parentEmbeddedUpdate = {};
451
+ let targetUpdate = {};
452
+ let targetEmbeddedUpdate = {};
453
+ let selfDeleted = false;
454
+ let rerender = false;
455
+ let document = this.document;
456
+ const context = {
457
+ object: this.document,
458
+ target: game.user.getTargetOrNothing()
459
+ };
460
+ // If this is an item, attach the parent
461
+ if (document.documentName === "Item" && document.parent) {
462
+ context.actor = document.parent;
463
+ }
464
+ else {
465
+ context.actor = document;
466
+ }
467
+ ${translateExpression(entry, id, action.method, false, action)}
468
+ if (!selfDeleted && Object.keys(update).length > 0) {
469
+ await this.document.update(update);
470
+ rerender = true;
471
+ }
472
+ if (!selfDeleted && Object.keys(embeddedUpdate).length > 0) {
473
+ for (let key of Object.keys(embeddedUpdate)) {
474
+ await this.document.updateEmbeddedDocuments("Item", embeddedUpdate[key]);
475
+ }
476
+ rerender = true;
477
+ }
478
+ if (Object.keys(parentUpdate).length > 0) {
479
+ await this.document.parent.update(parentUpdate);
480
+ rerender = true;
481
+ }
482
+ if (Object.keys(parentEmbeddedUpdate).length > 0) {
483
+ for (let key of Object.keys(parentEmbeddedUpdate)) {
484
+ await document.parent.updateEmbeddedDocuments("Item", parentEmbeddedUpdate[key]);
485
+ }
486
+ }
487
+ if (Object.keys(targetUpdate).length > 0) {
488
+ await context.target.update(targetUpdate);
489
+ }
490
+ if (Object.keys(targetEmbeddedUpdate).length > 0) {
491
+ for (let key of Object.keys(targetEmbeddedUpdate)) {
492
+ await context.target.updateEmbeddedDocuments("Item", targetEmbeddedUpdate[key]);
493
+ }
494
+ }
495
+ if (rerender) {
496
+ this.render();
497
+ }
498
+ }
499
499
  `;
500
500
  }
501
501
  // Generates a sheet method that runs an attribute's `function:` body with the same update/flush
@@ -504,64 +504,64 @@ export function generateDocumentVueSheet(entry, id, document, destination) {
504
504
  function generateAttributeFunctionMethod({ attr, param }) {
505
505
  const functionDef = param.function.ref;
506
506
  console.log(`Generating attribute function handler ${attr.name} -> ${functionDef.name} for ${document.name} Vue sheet.`);
507
- return expandToNode `
508
-
509
- /* -------------------------------------------- */
510
-
511
- async _on${attr.name}AttributeFunction(event) {
512
- if (event?.preventDefault) event.preventDefault();
513
- let update = {};
514
- let embeddedUpdate = {};
515
- let parentUpdate = {};
516
- let parentEmbeddedUpdate = {};
517
- let targetUpdate = {};
518
- let targetEmbeddedUpdate = {};
519
- let selfDeleted = false;
520
- let rerender = false;
521
- let document = this.document;
522
- const context = {
523
- object: this.document,
524
- target: game.user.getTargetOrNothing()
525
- };
526
- // If this is an item, attach the parent
527
- if (document.documentName === "Item" && document.parent) {
528
- context.actor = document.parent;
529
- }
530
- else {
531
- context.actor = document;
532
- }
533
- ${translateExpression(entry, id, functionDef.method, false, functionDef)}
534
- if (!selfDeleted && Object.keys(update).length > 0) {
535
- await this.document.update(update);
536
- rerender = true;
537
- }
538
- if (!selfDeleted && Object.keys(embeddedUpdate).length > 0) {
539
- for (let key of Object.keys(embeddedUpdate)) {
540
- await this.document.updateEmbeddedDocuments("Item", embeddedUpdate[key]);
541
- }
542
- rerender = true;
543
- }
544
- if (Object.keys(parentUpdate).length > 0) {
545
- await this.document.parent.update(parentUpdate);
546
- rerender = true;
547
- }
548
- if (Object.keys(parentEmbeddedUpdate).length > 0) {
549
- for (let key of Object.keys(parentEmbeddedUpdate)) {
550
- await document.parent.updateEmbeddedDocuments("Item", parentEmbeddedUpdate[key]);
551
- }
552
- }
553
- if (Object.keys(targetUpdate).length > 0) {
554
- await context.target.update(targetUpdate);
555
- }
556
- if (Object.keys(targetEmbeddedUpdate).length > 0) {
557
- for (let key of Object.keys(targetEmbeddedUpdate)) {
558
- await context.target.updateEmbeddedDocuments("Item", targetEmbeddedUpdate[key]);
559
- }
560
- }
561
- if (rerender) {
562
- this.render();
563
- }
564
- }
507
+ return expandToNode `
508
+
509
+ /* -------------------------------------------- */
510
+
511
+ async _on${attr.name}AttributeFunction(event) {
512
+ if (event?.preventDefault) event.preventDefault();
513
+ let update = {};
514
+ let embeddedUpdate = {};
515
+ let parentUpdate = {};
516
+ let parentEmbeddedUpdate = {};
517
+ let targetUpdate = {};
518
+ let targetEmbeddedUpdate = {};
519
+ let selfDeleted = false;
520
+ let rerender = false;
521
+ let document = this.document;
522
+ const context = {
523
+ object: this.document,
524
+ target: game.user.getTargetOrNothing()
525
+ };
526
+ // If this is an item, attach the parent
527
+ if (document.documentName === "Item" && document.parent) {
528
+ context.actor = document.parent;
529
+ }
530
+ else {
531
+ context.actor = document;
532
+ }
533
+ ${translateExpression(entry, id, functionDef.method, false, functionDef)}
534
+ if (!selfDeleted && Object.keys(update).length > 0) {
535
+ await this.document.update(update);
536
+ rerender = true;
537
+ }
538
+ if (!selfDeleted && Object.keys(embeddedUpdate).length > 0) {
539
+ for (let key of Object.keys(embeddedUpdate)) {
540
+ await this.document.updateEmbeddedDocuments("Item", embeddedUpdate[key]);
541
+ }
542
+ rerender = true;
543
+ }
544
+ if (Object.keys(parentUpdate).length > 0) {
545
+ await this.document.parent.update(parentUpdate);
546
+ rerender = true;
547
+ }
548
+ if (Object.keys(parentEmbeddedUpdate).length > 0) {
549
+ for (let key of Object.keys(parentEmbeddedUpdate)) {
550
+ await document.parent.updateEmbeddedDocuments("Item", parentEmbeddedUpdate[key]);
551
+ }
552
+ }
553
+ if (Object.keys(targetUpdate).length > 0) {
554
+ await context.target.update(targetUpdate);
555
+ }
556
+ if (Object.keys(targetEmbeddedUpdate).length > 0) {
557
+ for (let key of Object.keys(targetEmbeddedUpdate)) {
558
+ await context.target.updateEmbeddedDocuments("Item", targetEmbeddedUpdate[key]);
559
+ }
560
+ }
561
+ if (rerender) {
562
+ this.render();
563
+ }
564
+ }
565
565
  `;
566
566
  }
567
567
  }