mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-18 07:59:03 +01:00
Added the ability for effects to automatically activate/deactivate depending on range between tokens
This commit is contained in:
parent
4defe69c21
commit
b80f598625
13 changed files with 218 additions and 30 deletions
|
|
@ -6,7 +6,7 @@ import * as dice from './module/dice/_module.mjs';
|
||||||
import * as fields from './module/data/fields/_module.mjs';
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
import { compareValues, getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||||
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
|
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
|
||||||
import { DualityRollColor } from './module/data/settings/Appearance.mjs';
|
import { DualityRollColor } from './module/data/settings/Appearance.mjs';
|
||||||
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
|
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
|
||||||
|
|
@ -235,3 +235,73 @@ Hooks.on('renderJournalDirectory', async (tab, html, _, options) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Hooks.on('moveToken', async (movedToken, data) => {
|
||||||
|
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||||
|
if (!effectsAutomation.rangeDependent) return;
|
||||||
|
|
||||||
|
const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence.enabled);
|
||||||
|
|
||||||
|
const { x, y, height, width } = data.destination;
|
||||||
|
const getDimensions = (x, y, height, width) => {
|
||||||
|
const heightModifier = Math.max(Math.round(height) - 1, 0);
|
||||||
|
const widthModifier = Math.max(Math.round(width) - 1, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
minX: x - widthModifier,
|
||||||
|
maxX: x + widthModifier,
|
||||||
|
minY: y - heightModifier,
|
||||||
|
maxY: y + widthModifier
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const { minX: dMinX, maxX: dMaxX, minY: dMinY, maxY: dMaxY } = getDimensions(x, y, height, width);
|
||||||
|
|
||||||
|
const updateEffects = async (token, dimensions, effects, effectUpdates) => {
|
||||||
|
const { minX, maxX, minY, maxY } = dimensions;
|
||||||
|
|
||||||
|
const rangeMeasurement = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement);
|
||||||
|
|
||||||
|
for (let effect of effects.filter(x => x.system.rangeDependence.enabled)) {
|
||||||
|
const { target, range, type } = effect.system.rangeDependence;
|
||||||
|
if (
|
||||||
|
(target === 'friendly' && token.disposition !== 1) ||
|
||||||
|
(target === 'hostile' && token.disposition !== -1)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const distance = rangeMeasurement[range] * 20; // x/y scale is 100. Grid in feet is 5.
|
||||||
|
const xOkay =
|
||||||
|
compareValues(Math.abs(minX - dMinX), distance, type) ||
|
||||||
|
compareValues(Math.abs(maxX - dMaxX), distance, type);
|
||||||
|
const yOkay =
|
||||||
|
compareValues(Math.abs(minY - dMinY), distance, type) ||
|
||||||
|
compareValues(Math.abs(maxY - dMaxY), distance, type);
|
||||||
|
|
||||||
|
const reverse = ['moreThan', 'moreThanEqual'].includes(type);
|
||||||
|
const newDisabled = reverse ? !xOkay && !yOkay : !xOkay || !yOkay;
|
||||||
|
const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
|
||||||
|
effectUpdates[effect.uuid] = {
|
||||||
|
disabled: reverse ? oldDisabled || newDisabled : oldDisabled && newDisabled,
|
||||||
|
value: effect
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const effectUpdates = {};
|
||||||
|
for (let token of game.scenes.find(x => x.active).tokens) {
|
||||||
|
if (token.id === movedToken.id) continue;
|
||||||
|
|
||||||
|
const { x, y, height, width } = token;
|
||||||
|
const dimensions = getDimensions(x, y, height, width);
|
||||||
|
|
||||||
|
await updateEffects(token, dimensions, rangeDependantEffects, effectUpdates);
|
||||||
|
|
||||||
|
if (token.actor) await updateEffects(token, dimensions, token.actor.effects, effectUpdates);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in effectUpdates) {
|
||||||
|
const effect = effectUpdates[key];
|
||||||
|
await effect.value.update({ disabled: effect.disabled });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
24
lang/en.json
24
lang/en.json
|
|
@ -88,6 +88,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ACTIVEEFFECT": {
|
||||||
|
"Config": {
|
||||||
|
"rangeDependence": {
|
||||||
|
"title": "Range Dependence"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RangeDependance": {
|
||||||
|
"hint": "Settings for an optional distance at which this effect should activate",
|
||||||
|
"title": "Range Dependant"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ACTORS": {
|
"ACTORS": {
|
||||||
"Adversary": {
|
"Adversary": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
|
@ -622,6 +633,13 @@
|
||||||
"oneHanded": "One-Handed",
|
"oneHanded": "One-Handed",
|
||||||
"twoHanded": "Two-Handed"
|
"twoHanded": "Two-Handed"
|
||||||
},
|
},
|
||||||
|
"CompareOperator": {
|
||||||
|
"lessThan": "Less Than",
|
||||||
|
"lessThanEqual": "Less Than Equal",
|
||||||
|
"equal": "Equal",
|
||||||
|
"moreThanEqual": "More Than Equal",
|
||||||
|
"moreThan": "More Than"
|
||||||
|
},
|
||||||
"Condition": {
|
"Condition": {
|
||||||
"dead": {
|
"dead": {
|
||||||
"name": "Dead",
|
"name": "Dead",
|
||||||
|
|
@ -1546,6 +1564,12 @@
|
||||||
"hordeDamage": {
|
"hordeDamage": {
|
||||||
"label": "Automatic Horde Damage",
|
"label": "Automatic Horde Damage",
|
||||||
"hint": "Automatically active horde effect to lower damage when reaching half or lower HP."
|
"hint": "Automatically active horde effect to lower damage when reaching half or lower HP."
|
||||||
|
},
|
||||||
|
"effects": {
|
||||||
|
"rangeDependent": {
|
||||||
|
"label": "Effect Range Dependent",
|
||||||
|
"hint": "Effects with defined range dependency will automatically turn on/off depending on range"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
|
||||||
tabs: { template: 'templates/generic/tab-navigation.hbs' },
|
tabs: { template: 'templates/generic/tab-navigation.hbs' },
|
||||||
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
|
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
|
||||||
duration: { template: 'systems/daggerheart/templates/sheets/activeEffect/duration.hbs' },
|
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
|
||||||
changes: {
|
changes: {
|
||||||
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
|
||||||
scrollable: ['ol[data-changes]']
|
scrollable: ['ol[data-changes]']
|
||||||
|
|
@ -39,7 +39,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
sheet: {
|
sheet: {
|
||||||
tabs: [
|
tabs: [
|
||||||
{ id: 'details', icon: 'fa-solid fa-book' },
|
{ id: 'details', icon: 'fa-solid fa-book' },
|
||||||
{ id: 'duration', icon: 'fa-solid fa-clock' },
|
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
|
||||||
{ id: 'changes', icon: 'fa-solid fa-gears' }
|
{ id: 'changes', icon: 'fa-solid fa-gears' }
|
||||||
],
|
],
|
||||||
initial: 'details',
|
initial: 'details',
|
||||||
|
|
|
||||||
|
|
@ -43,25 +43,6 @@ export const actionTypes = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const targetTypes = {
|
|
||||||
self: {
|
|
||||||
id: 'self',
|
|
||||||
label: 'Self'
|
|
||||||
},
|
|
||||||
friendly: {
|
|
||||||
id: 'friendly',
|
|
||||||
label: 'Friendly'
|
|
||||||
},
|
|
||||||
hostile: {
|
|
||||||
id: 'hostile',
|
|
||||||
label: 'Hostile'
|
|
||||||
},
|
|
||||||
any: {
|
|
||||||
id: 'any',
|
|
||||||
label: 'Any'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const damageOnSave = {
|
export const damageOnSave = {
|
||||||
none: {
|
none: {
|
||||||
id: 'none',
|
id: 'none',
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,52 @@ export const range = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const compareOperator = {
|
||||||
|
lessThan: {
|
||||||
|
id: 'lessThan',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CompareOperator.lessThan'
|
||||||
|
},
|
||||||
|
lessThanEqual: {
|
||||||
|
id: 'lessThanEqual',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CompareOperator.lessThanEqual'
|
||||||
|
},
|
||||||
|
equal: {
|
||||||
|
id: 'equal',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CompareOperator.equal'
|
||||||
|
},
|
||||||
|
moreThanEqual: {
|
||||||
|
id: 'moreThanEqual',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CompareOperator.moreThanEqual'
|
||||||
|
},
|
||||||
|
moreThan: {
|
||||||
|
id: 'moreThan',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CompareOperator.moreThan'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const otherTargetTypes = {
|
||||||
|
friendly: {
|
||||||
|
id: 'friendly',
|
||||||
|
label: 'Friendly'
|
||||||
|
},
|
||||||
|
hostile: {
|
||||||
|
id: 'hostile',
|
||||||
|
label: 'Hostile'
|
||||||
|
},
|
||||||
|
any: {
|
||||||
|
id: 'any',
|
||||||
|
label: 'Any'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const targetTypes = {
|
||||||
|
self: {
|
||||||
|
id: 'self',
|
||||||
|
label: 'Self'
|
||||||
|
},
|
||||||
|
...otherTargetTypes
|
||||||
|
};
|
||||||
|
|
||||||
export const burden = {
|
export const burden = {
|
||||||
oneHanded: {
|
oneHanded: {
|
||||||
value: 'oneHanded',
|
value: 'oneHanded',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import BaseEffect from './baseEffect.mjs';
|
||||||
import BeastformEffect from './beastformEffect.mjs';
|
import BeastformEffect from './beastformEffect.mjs';
|
||||||
|
|
||||||
export { BeastformEffect };
|
export { BaseEffect, BeastformEffect };
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
base: BaseEffect,
|
||||||
beastform: BeastformEffect
|
beastform: BeastformEffect
|
||||||
};
|
};
|
||||||
|
|
|
||||||
33
module/data/activeEffect/baseEffect.mjs
Normal file
33
module/data/activeEffect/baseEffect.mjs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
rangeDependence: new fields.SchemaField({
|
||||||
|
enabled: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.GENERAL.enabled'
|
||||||
|
}),
|
||||||
|
type: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.compareOperator,
|
||||||
|
initial: CONFIG.DH.GENERAL.compareOperator.lessThanEqual.id,
|
||||||
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
|
}),
|
||||||
|
target: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.otherTargetTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.otherTargetTypes.hostile.id,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Target.single'
|
||||||
|
}),
|
||||||
|
range: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.range,
|
||||||
|
initial: CONFIG.DH.GENERAL.range.melee.id,
|
||||||
|
label: 'DAGGERHEART.GENERAL.range'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { updateActorTokens } from '../../helpers/utils.mjs';
|
import { updateActorTokens } from '../../helpers/utils.mjs';
|
||||||
|
import BaseEffect from './baseEffect.mjs';
|
||||||
|
|
||||||
export default class BeastformEffect extends foundry.abstract.TypeDataModel {
|
export default class BeastformEffect extends BaseEffect {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ export default class TargetField extends fields.SchemaField {
|
||||||
constructor(options = {}, context = {}) {
|
constructor(options = {}, context = {}) {
|
||||||
const targetFields = {
|
const targetFields = {
|
||||||
type: new fields.StringField({
|
type: new fields.StringField({
|
||||||
choices: CONFIG.DH.ACTIONS.targetTypes,
|
choices: CONFIG.DH.GENERAL.targetTypes,
|
||||||
initial: CONFIG.DH.ACTIONS.targetTypes.any.id,
|
initial: CONFIG.DH.GENERAL.targetTypes.any.id,
|
||||||
nullable: true
|
nullable: true
|
||||||
}),
|
}),
|
||||||
amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
|
amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
|
||||||
|
|
@ -16,11 +16,11 @@ export default class TargetField extends fields.SchemaField {
|
||||||
static prepareConfig(config) {
|
static prepareConfig(config) {
|
||||||
if (!this.target?.type) return [];
|
if (!this.target?.type) return [];
|
||||||
let targets;
|
let targets;
|
||||||
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
|
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
|
||||||
targets = [this.actor.token ?? this.actor.prototypeToken];
|
targets = [this.actor.token ?? this.actor.prototypeToken];
|
||||||
else {
|
else {
|
||||||
targets = Array.from(game.user.targets);
|
targets = Array.from(game.user.targets);
|
||||||
if (this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) {
|
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
|
||||||
targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
|
targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
|
||||||
if (this.target.amount && targets.length > this.target.amount) targets = [];
|
if (this.target.amount && targets.length > this.target.amount) targets = [];
|
||||||
}
|
}
|
||||||
|
|
@ -43,9 +43,9 @@ export default class TargetField extends fields.SchemaField {
|
||||||
: this.actor.prototypeToken.disposition,
|
: this.actor.prototypeToken.disposition,
|
||||||
targetDisposition = target.document.disposition;
|
targetDisposition = target.document.disposition;
|
||||||
return (
|
return (
|
||||||
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.friendly.id &&
|
(this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
|
||||||
actorDisposition === targetDisposition) ||
|
actorDisposition === targetDisposition) ||
|
||||||
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.hostile.id &&
|
(this.target.type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
|
||||||
actorDisposition + targetDisposition === 0)
|
actorDisposition + targetDisposition === 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
required: true,
|
required: true,
|
||||||
initial: true,
|
initial: true,
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hordeDamage.label'
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hordeDamage.label'
|
||||||
|
}),
|
||||||
|
effects: new fields.SchemaField({
|
||||||
|
rangeDependent: new fields.BooleanField({
|
||||||
|
initial: true,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.effects.rangeDependent.label'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -321,3 +321,18 @@ export const arraysEqual = (a, b) =>
|
||||||
[...new Set([...a, ...b])].every(v => a.filter(e => e === v).length === b.filter(e => e === v).length);
|
[...new Set([...a, ...b])].every(v => a.filter(e => e === v).length === b.filter(e => e === v).length);
|
||||||
|
|
||||||
export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));
|
export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));
|
||||||
|
|
||||||
|
export function compareValues(a, b, compare) {
|
||||||
|
switch (compare) {
|
||||||
|
case CONFIG.DH.GENERAL.compareOperator.lessThan.id:
|
||||||
|
return a < b;
|
||||||
|
case CONFIG.DH.GENERAL.compareOperator.lessThanEqual.id:
|
||||||
|
return a <= b;
|
||||||
|
case CONFIG.DH.GENERAL.compareOperator.equal.id:
|
||||||
|
return a === b;
|
||||||
|
case CONFIG.DH.GENERAL.compareOperator.moreThanEqual.id:
|
||||||
|
return a >= b;
|
||||||
|
case CONFIG.DH.GENERAL.compareOperator.moreThan.id:
|
||||||
|
return a > b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
|
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
|
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
|
||||||
|
{{formGroup settingFields.schema.fields.effects.fields.rangeDependent value=settingFields._source.effects.rangeDependent localize=true}}
|
||||||
|
|
||||||
<footer class="form-footer">
|
<footer class="form-footer">
|
||||||
<button data-action="reset">
|
<button data-action="reset">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
<section class="tab{{#if tab.active}} active{{/if}}" data-group="{{tab.group}}" data-tab="{{tab.id}}">
|
<section class="tab{{#if tab.active}} active{{/if}}" data-group="{{tab.group}}" data-tab="{{tab.id}}">
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}}</legend>
|
||||||
|
|
||||||
|
{{formGroup document.system.schema.fields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }}
|
||||||
|
{{formGroup document.system.schema.fields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }}
|
||||||
|
{{formGroup document.system.schema.fields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }}
|
||||||
|
{{formGroup document.system.schema.fields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="one-column">
|
<fieldset class="one-column">
|
||||||
{{formGroup fields.duration.fields.seconds value=source.duration.seconds rootId=rootId}}
|
{{formGroup fields.duration.fields.seconds value=source.duration.seconds rootId=rootId}}
|
||||||
{{formGroup fields.duration.fields.startTime value=source.duration.startTime rootId=rootId}}
|
{{formGroup fields.duration.fields.startTime value=source.duration.startTime rootId=rootId}}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue