intelligent-system-design-language 0.3.22 → 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 (91) 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 -1444
  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 -1444
  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 -654
  27. package/out/cli/components/css-generator.js +4 -4
  28. package/out/cli/components/damage-roll-generator.js +160 -160
  29. package/out/cli/components/datamodel-generator.js +257 -257
  30. package/out/cli/components/derived-data-generator.js +923 -923
  31. package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
  32. package/out/cli/components/init-hook-generator.js +495 -495
  33. package/out/cli/components/measured-template-preview.js +221 -221
  34. package/out/cli/components/method-generator.js +548 -548
  35. package/out/cli/components/ready-hook-generator.js +404 -404
  36. package/out/cli/components/token-generator.js +116 -116
  37. package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
  38. package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
  39. package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
  40. package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
  41. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
  42. package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
  43. package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
  44. package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
  45. package/out/cli/components/vue/base-components/vue-dice.js +98 -98
  46. package/out/cli/components/vue/base-components/vue-die.js +73 -73
  47. package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
  48. package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
  49. package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
  50. package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
  51. package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
  52. package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
  53. package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
  54. package/out/cli/components/vue/base-components/vue-money.js +483 -483
  55. package/out/cli/components/vue/base-components/vue-number.js +174 -174
  56. package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
  57. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
  58. package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
  59. package/out/cli/components/vue/base-components/vue-resource.js +136 -136
  60. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +285 -285
  61. package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
  62. package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
  63. package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
  64. package/out/cli/components/vue/base-components/vue-string.js +60 -60
  65. package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
  66. package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
  67. package/out/cli/components/vue/vue-action-component-generator.js +64 -64
  68. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
  69. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
  70. package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
  71. package/out/cli/components/vue/vue-document-creation-app.js +121 -121
  72. package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
  73. package/out/cli/components/vue/vue-generator.js +40 -40
  74. package/out/cli/components/vue/vue-mixin.js +296 -296
  75. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
  76. package/out/cli/components/vue/vue-prompt-generator.js +80 -80
  77. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
  78. package/out/cli/components/vue/vue-sheet-application-generator.js +1171 -1171
  79. package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
  80. package/out/cli/generator.js +438 -438
  81. package/out/extension/github/githubManager.js +35 -35
  82. package/out/extension/github/githubQuickActions.js +120 -120
  83. package/out/extension/github/system-workflow.yml +47 -47
  84. package/out/extension/main.cjs.map +1 -1
  85. package/out/extension/package.json +419 -419
  86. package/out/language/generated/grammar.js +14240 -14240
  87. package/out/language/main.cjs.map +1 -1
  88. package/out/package.json +419 -419
  89. package/out/progressbar.min.js +6 -6
  90. package/out/styles.scss +762 -762
  91. 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
  }