mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
[Fix] Party Fixes (#1284)
* Fixed deletion of characters in the world locking up the party actor * . * Fixed so leader in group roll gains resourcse * Fixed so party.inventory has the right controls * Corrected for added character purning * .
This commit is contained in:
parent
7df43d71e0
commit
481ce46edf
12 changed files with 104 additions and 67 deletions
|
|
@ -2624,7 +2624,7 @@
|
||||||
"cardTooHighLevel": "The card is too high level!",
|
"cardTooHighLevel": "The card is too high level!",
|
||||||
"duplicateCard": "You cannot select the same card more than once.",
|
"duplicateCard": "You cannot select the same card more than once.",
|
||||||
"duplicateCharacter": "This actor is already registered in the party members list.",
|
"duplicateCharacter": "This actor is already registered in the party members list.",
|
||||||
"onlyCharactersInPartySheet": "You can drag only characters to a party sheet.",
|
"onlyCharactersInPartySheet": "You can only drag characters, companions and adverasries to the party sheet.",
|
||||||
"notPrimary": "The weapon is not a primary weapon!",
|
"notPrimary": "The weapon is not a primary weapon!",
|
||||||
"notSecondary": "The weapon is not a secondary weapon!",
|
"notSecondary": "The weapon is not a secondary weapon!",
|
||||||
"itemTooHighTier": "The item must be from Tier1",
|
"itemTooHighTier": "The item must be from Tier1",
|
||||||
|
|
@ -2661,7 +2661,8 @@
|
||||||
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
||||||
"gmRequired": "This action requires an online GM",
|
"gmRequired": "This action requires an online GM",
|
||||||
"gmOnly": "This can only be accessed by the GM",
|
"gmOnly": "This can only be accessed by the GM",
|
||||||
"noActorOwnership": "You do not have permissions for this character"
|
"noActorOwnership": "You do not have permissions for this character",
|
||||||
|
"documentIsMissing": "The {documentType} is missing from the world."
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
import DHItem from '../../../documents/item.mjs';
|
import DHItem from '../../../documents/item.mjs';
|
||||||
|
import DhParty from '../../../data/actor/party.mjs';
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -79,6 +80,9 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
||||||
|
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||||
|
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
this._createFilterMenus();
|
this._createFilterMenus();
|
||||||
|
|
@ -277,13 +281,17 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #tagTeamRoll() {
|
static async #tagTeamRoll() {
|
||||||
new game.system.api.applications.dialogs.TagTeamDialog(this.document.system.partyMembers).render({
|
new game.system.api.applications.dialogs.TagTeamDialog(
|
||||||
|
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
).render({
|
||||||
force: true
|
force: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(params) {
|
static async #groupRoll(_params) {
|
||||||
new GroupRollDialog(this.document.system.partyMembers).render({ force: true });
|
new GroupRollDialog(
|
||||||
|
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -453,17 +461,17 @@ export default class Party extends DHBaseActorSheet {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
const item = await foundry.utils.fromUuid(data.uuid);
|
const document = await foundry.utils.fromUuid(data.uuid);
|
||||||
|
|
||||||
if (item instanceof DhpActor) {
|
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
||||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
if (currentMembers.includes(data.uuid)) {
|
if (currentMembers.includes(data.uuid)) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.document.update({ 'system.partyMembers': [...currentMembers, item.uuid] });
|
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
|
||||||
} else if (item instanceof DHItem) {
|
} else if (document instanceof DHItem) {
|
||||||
this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
this.document.createEmbeddedDocuments('Item', [document.toObject()]);
|
||||||
} else {
|
} else {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,9 +192,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
|
|
||||||
async groupRollButton(event, message) {
|
async groupRollButton(event, message) {
|
||||||
const path = event.currentTarget.dataset.path;
|
const path = event.currentTarget.dataset.path;
|
||||||
|
const isLeader = path === 'leader';
|
||||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
||||||
const actor = game.actors.get(actorData._id);
|
const actor = game.actors.get(actorData._id);
|
||||||
|
|
||||||
|
if (!actor) {
|
||||||
|
return ui.notifications.error(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', {
|
||||||
|
documentType: game.i18n.localize('TYPES.Actor.character')
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +223,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
skips: {
|
skips: {
|
||||||
createMessage: true,
|
createMessage: true,
|
||||||
resources: true
|
resources: !isLeader
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const result = await actor.diceRoll({
|
const result = await actor.diceRoll({
|
||||||
|
|
@ -225,6 +234,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
await game.system.api.fields.ActionFields.CostField.execute.call({ actor }, result);
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
const newMessageData = foundry.utils.deepClone(message.system);
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
||||||
|
|
|
||||||
|
|
@ -675,6 +675,8 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTags() {
|
_getTags() {
|
||||||
return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter((t) => !!t);
|
return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter(
|
||||||
|
t => !!t
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ export default class DhParty extends BaseDataActor {
|
||||||
|
|
||||||
// Clear this party from all members that aren't deleted
|
// Clear this party from all members that aren't deleted
|
||||||
for (const member of this.partyMembers) {
|
for (const member of this.partyMembers) {
|
||||||
member.parties?.delete(this.parent);
|
member?.parties?.delete(this.parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export async function runMigrations() {
|
||||||
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
||||||
if (!lastMigrationVersion) lastMigrationVersion = game.system.version;
|
if (!lastMigrationVersion) lastMigrationVersion = game.system.version;
|
||||||
|
|
||||||
|
//#region old migrations
|
||||||
if (foundry.utils.isNewerVersion('1.1.0', lastMigrationVersion)) {
|
if (foundry.utils.isNewerVersion('1.1.0', lastMigrationVersion)) {
|
||||||
const lockedPacks = [];
|
const lockedPacks = [];
|
||||||
const compendiumActors = [];
|
const compendiumActors = [];
|
||||||
|
|
@ -190,6 +191,7 @@ export async function runMigrations() {
|
||||||
|
|
||||||
lastMigrationVersion = '1.2.0';
|
lastMigrationVersion = '1.2.0';
|
||||||
}
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ body.game:is(.performance-low, .noblur) {
|
||||||
background: light-dark(@dark-blue-40, @dark-golden-40);
|
background: light-dark(@dark-blue-40, @dark-golden-40);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid light-dark(@dark-blue, @golden);
|
border: 1px solid light-dark(@dark-blue, @golden);
|
||||||
max-width: 230px;
|
width: 230px;
|
||||||
height: -webkit-fill-available;
|
height: -webkit-fill-available;
|
||||||
|
|
||||||
.actor-name {
|
.actor-name {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
"id": "daggerheart",
|
"id": "daggerheart",
|
||||||
"title": "Daggerheart",
|
"title": "Daggerheart",
|
||||||
"description": "An unofficial implementation of the Daggerheart system",
|
"description": "An unofficial implementation of the Daggerheart system",
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13",
|
"minimum": "13",
|
||||||
"verified": "13.350",
|
"verified": "13.351",
|
||||||
"maximum": "13"
|
"maximum": "13"
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
item=actor
|
item=actor
|
||||||
type='character'
|
type='character'
|
||||||
isActor=true
|
isActor=true
|
||||||
|
hideContextMenu=true
|
||||||
}}
|
}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
<h2 class="actor-name">{{actor.name}}</h2>
|
<h2 class="actor-name">{{actor.name}}</h2>
|
||||||
<img class="actor-img" src="{{actor.img}}">
|
<img class="actor-img" src="{{actor.img}}">
|
||||||
<div class="resources">
|
<div class="resources">
|
||||||
|
{{#unless (eq actor.type 'companion') }}
|
||||||
<div class="slot-section">
|
<div class="slot-section">
|
||||||
<div class="slot-bar">
|
<div class="slot-bar">
|
||||||
{{#times actor.system.resources.hitPoints.max}}
|
{{#times actor.system.resources.hitPoints.max}}
|
||||||
|
|
@ -35,6 +36,7 @@
|
||||||
<span class="value">{{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}}</span>
|
<span class="value">{{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
<div class="slot-section">
|
<div class="slot-section">
|
||||||
<div class="slot-bar">
|
<div class="slot-bar">
|
||||||
|
|
@ -71,7 +73,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
{{#unless (or (eq actor.type 'companion') (eq actor.type 'adversary')) }}
|
||||||
<div class="hope-section">
|
<div class="hope-section">
|
||||||
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
|
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
|
||||||
{{#times actor.system.resources.hope.max}}
|
{{#times actor.system.resources.hope.max}}
|
||||||
|
|
@ -84,6 +86,9 @@
|
||||||
</span>
|
</span>
|
||||||
{{/times}}
|
{{/times}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
|
{{#unless (eq actor.type 'companion')}}
|
||||||
<div class="threshold-section">
|
<div class="threshold-section">
|
||||||
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}</h4>
|
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}</h4>
|
||||||
<h4 class="threshold-value">{{actor.system.damageThresholds.major}}</h4>
|
<h4 class="threshold-value">{{actor.system.damageThresholds.major}}</h4>
|
||||||
|
|
@ -91,6 +96,7 @@
|
||||||
<h4 class="threshold-value">{{actor.system.damageThresholds.severe}}</h4>
|
<h4 class="threshold-value">{{actor.system.damageThresholds.severe}}</h4>
|
||||||
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}</h4>
|
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ Parameters:
|
||||||
- isGlassy {boolean} : If true, applies the 'glassy' class to the fieldset.
|
- isGlassy {boolean} : If true, applies the 'glassy' class to the fieldset.
|
||||||
- cardView {boolean} : If true and type is 'domainCard', renders using domain card layout.
|
- cardView {boolean} : If true and type is 'domainCard', renders using domain card layout.
|
||||||
- isActor {boolean} : Passed through to inventory-item partials.
|
- isActor {boolean} : Passed through to inventory-item partials.
|
||||||
|
- isItem {boolean} : Passed through to inventory-item partials
|
||||||
- actorType {boolean} : The actor type of the parent actor
|
- actorType {boolean} : The actor type of the parent actor
|
||||||
- canCreate {boolean} : If true, show createDoc anchor on legend
|
- canCreate {boolean} : If true, show createDoc anchor on legend
|
||||||
- inVault {boolean} : If true, the domainCard is created with inVault=true
|
- inVault {boolean} : If true, the domainCard is created with inVault=true
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
Parameters:
|
Parameters:
|
||||||
- type {string} : The type of items in the list
|
- type {string} : The type of items in the list
|
||||||
- isActor {boolean} : Passed through to inventory-item partials.
|
- isActor {boolean} : Passed through to inventory-item partials.
|
||||||
|
- isItem {boolean} : Passed through to inventory-item partials
|
||||||
- actorType {boolean} : The actor type of the parent actor
|
- actorType {boolean} : The actor type of the parent actor
|
||||||
- categoryAdversary {string} : Category adversary id.
|
- categoryAdversary {string} : Category adversary id.
|
||||||
- noExtensible {boolean} : If true, the inventory-item-content would be collapsable/extendible else it always be showed
|
- noExtensible {boolean} : If true, the inventory-item-content would be collapsable/extendible else it always be showed
|
||||||
|
|
@ -18,7 +19,7 @@ Parameters:
|
||||||
|
|
||||||
<li class="inventory-item" data-item-id="{{item.id}}" {{#if (or (eq type 'action' ) (eq type 'attack' ))}}
|
<li class="inventory-item" data-item-id="{{item.id}}" {{#if (or (eq type 'action' ) (eq type 'attack' ))}}
|
||||||
data-action-id="{{item.id}}" {{/if}} data-item-uuid="{{item.uuid}}" data-type="{{type}}" data-no-compendium-edit="{{noCompendiumEdit}}" draggable="true">
|
data-action-id="{{item.id}}" {{/if}} data-item-uuid="{{item.uuid}}" data-type="{{type}}" data-no-compendium-edit="{{noCompendiumEdit}}" draggable="true">
|
||||||
<div class="inventory-item-header {{#if (eq actorType 'party')}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
|
<div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
|
||||||
{{!-- Image --}}
|
{{!-- Image --}}
|
||||||
<div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen
|
<div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen
|
||||||
(hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}}
|
(hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}}
|
||||||
|
|
@ -94,11 +95,6 @@ Parameters:
|
||||||
<i class="fa-solid fa-shield"></i>
|
<i class="fa-solid fa-shield"></i>
|
||||||
</a>
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
|
||||||
<a data-action="deleteItem" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
|
|
||||||
<i class="fa-solid fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
{{/unless}}
|
|
||||||
{{#if (eq type 'domainCard')}}
|
{{#if (eq type 'domainCard')}}
|
||||||
<a data-action="toggleVault"
|
<a data-action="toggleVault"
|
||||||
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.inVault 'sendToLoadout' 'sendToVault' }}">
|
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.inVault 'sendToLoadout' 'sendToVault' }}">
|
||||||
|
|
@ -110,11 +106,19 @@ Parameters:
|
||||||
<i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}"></i>
|
<i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}"></i>
|
||||||
</a>
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (and (hasProperty item "toChat") (not (eq actorType 'party')))}}
|
{{#if (hasProperty item "toChat")}}
|
||||||
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
|
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
|
||||||
<i class="fa-regular fa-message"></i>
|
<i class="fa-regular fa-message"></i>
|
||||||
</a>
|
</a>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{else}}
|
||||||
|
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.openActorWorld">
|
||||||
|
<i class="fa-solid fa-globe"></i>
|
||||||
|
</a>
|
||||||
|
<a data-action="deleteItem" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
{{/unless}}
|
||||||
{{#unless hideContextMenu}}
|
{{#unless hideContextMenu}}
|
||||||
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
|
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
|
||||||
<i class="fa-solid fa-ellipsis-vertical"></i>
|
<i class="fa-solid fa-ellipsis-vertical"></i>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue