From 3532898be9d41839a820e9a2ece045d3502eca08 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sat, 14 Jan 2023 14:20:39 +0200 Subject: [PATCH 001/117] updated jsconfig --- app/jsconfig.json | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/jsconfig.json b/app/jsconfig.json index b97e629d..e4b479a3 100644 --- a/app/jsconfig.json +++ b/app/jsconfig.json @@ -10,14 +10,25 @@ "paths": { "/*": [ "./*" + ], + "meteor/aldeed:collection2": [ + "packages\\collection2\\collection2.js" ] - } + }, + "checkJs": true, + "allowJs": true }, "vueCompilerOptions": { - "target": 2, + "target": 2 }, "exclude": [ "node_modules", - "**/node_modules/*" - ] + "**/node_modules/*", + ".meteor" + ], + "typeAcquisition": { + "include": [ + "meteor" + ] + } } \ No newline at end of file From 72d932538b4ffc6e5c566642930d0667612d3cfc Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 31 Jan 2023 14:51:22 +0200 Subject: [PATCH 002/117] Fixed bug where rolls could not set strings --- .../actions/applyPropertyByType/applyRoll.js | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js index 7d860948..2a0a9002 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js @@ -4,22 +4,22 @@ import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/appl import resolve, { toString } from '/imports/parser/resolve.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; -export default function applyRoll(node, actionContext){ +export default function applyRoll(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; - const applyChildren = function(){ + const applyChildren = function () { applyNodeTriggers(node, 'after', actionContext); node.children.forEach(child => applyProperty(child, actionContext)); }; - if (prop.roll?.calculation){ + if (prop.roll?.calculation) { const logValue = []; // roll the dice only and store that string applyEffectsToCalculationParseNode(prop.roll, actionContext); - const {result: rolled, context} = resolve('roll', prop.roll.parseNode, actionContext.scope); - if (rolled.parseType !== 'constant'){ + const { result: rolled, context } = resolve('roll', prop.roll.parseNode, actionContext.scope); + if (rolled.parseType !== 'constant') { logValue.push(toString(rolled)); } logErrors(context.errors, actionContext); @@ -28,20 +28,20 @@ export default function applyRoll(node, actionContext){ context.errors = []; // Resolve the roll to a final value - const {result: reduced} = resolve('reduce', rolled, actionContext.scope, context); + const { result: reduced } = resolve('reduce', rolled, actionContext.scope, context); logErrors(context.errors, actionContext); // Store the result - if (reduced.parseType === 'constant'){ + if (reduced.parseType === 'constant') { prop.roll.value = reduced.value; - } else if (reduced.parseType === 'error'){ + } else if (reduced.parseType === 'error') { prop.roll.value = null; } else { prop.roll.value = toString(reduced); } - // If we didn't end up with a constant of finite amount, give up - if (reduced?.parseType !== 'constant' || !isFinite(reduced.value)){ + // If we didn't end up with a constant or a number of finite value, give up + if (reduced?.parseType !== 'constant' || (reduced.valueType === 'number' && !isFinite(reduced.value))) { return applyChildren(); } const value = reduced.value; @@ -49,7 +49,7 @@ export default function applyRoll(node, actionContext){ actionContext.scope[prop.variableName] = value; logValue.push(`**${value}**`); - if (!prop.silent){ + if (!prop.silent) { actionContext.addLog({ name: prop.name, value: logValue.join('\n'), From cbbbcaf56afb7f4f601e36be306692523dd0f382 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 31 Jan 2023 14:51:39 +0200 Subject: [PATCH 003/117] Changed variable name regex to allow $ and _ --- app/imports/constants/VARIABLE_NAME_REGEX.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/imports/constants/VARIABLE_NAME_REGEX.js b/app/imports/constants/VARIABLE_NAME_REGEX.js index ac7c1804..2ff1621f 100644 --- a/app/imports/constants/VARIABLE_NAME_REGEX.js +++ b/app/imports/constants/VARIABLE_NAME_REGEX.js @@ -1,4 +1,4 @@ // Must contain a letter, and be made of word characters only -const VARIABLE_NAME_REGEX = /^[a-z][\w-]*$/i; +const VARIABLE_NAME_REGEX = /^[a-z$][\w_]*$/i; export default VARIABLE_NAME_REGEX; From 16f5fe91ead77c1e2a7ec95c8587ee7d3bda4356 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 31 Jan 2023 15:55:02 +0200 Subject: [PATCH 004/117] Normalized all scope['$...'] to be unwrapped values --- .../applyPropertyByType/applyAction.js | 16 ++++++------ .../applyPropertyByType/applyBranch.js | 26 +++++++++---------- .../applyPropertyByType/applyDamage.js | 2 +- .../applyPropertyByType/applySavingThrow.js | 12 ++++----- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 021370d4..7587f9cc 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -72,10 +72,10 @@ function applyAttackWithoutTarget({ attack, actionContext }) { name += ' (Disadvantage)'; } if (!criticalMiss) { - scope['$attackHit'] = { value: true } + scope['$attackHit'] = true } if (!criticalHit) { - scope['$attackMiss'] = { value: true }; + scope['$attackMiss'] = true; } actionContext.addLog({ @@ -121,9 +121,9 @@ function applyAttackToTarget({ attack, target, actionContext }) { inline: true, }); if (criticalMiss || result < armor) { - scope['$attackMiss'] = { value: true }; + scope['$attackMiss'] = true; } else { - scope['$attackHit'] = { value: true }; + scope['$attackHit'] = true; } } else { actionContext.addLog({ @@ -163,9 +163,9 @@ function rollAttack(attack, scope) { value = rollDice(1, 20)[0]; resultPrefix = `1d20 [${value}] ${rollModifierText}` } - scope['$attackDiceRoll'] = { value }; + scope['$attackDiceRoll'] = value; const result = value + attack.value; - scope['$attackRoll'] = { value: result }; + scope['$attackRoll'] = result; const { criticalHit, criticalMiss } = applyCrits(value, scope); return { resultPrefix, result, value, criticalHit, criticalMiss }; } @@ -175,11 +175,11 @@ function applyCrits(value, scope) { let criticalHit = value >= criticalHitTarget; let criticalMiss; if (criticalHit) { - scope['$criticalHit'] = { value: true }; + scope['$criticalHit'] = true; } else { criticalMiss = value === 1; if (criticalMiss) { - scope['$criticalMiss'] = { value: true }; + scope['$criticalMiss'] = true; } } return { criticalHit, criticalMiss }; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js index ce918c4a..e02ac55a 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js @@ -3,22 +3,22 @@ import recalculateCalculation from './shared/recalculateCalculation.js'; import rollDice from '/imports/parser/rollDice.js'; import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'; -export default function applyBranch(node, actionContext){ +export default function applyBranch(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); - const applyChildren = function(){ + const applyChildren = function () { applyNodeTriggers(node, 'after', actionContext); node.children.forEach(child => applyProperty(child, actionContext)); }; const scope = actionContext.scope; const targets = actionContext.targets; const prop = node.node; - switch(prop.branchType){ + switch (prop.branchType) { case 'if': recalculateCalculation(prop.condition, actionContext); if (prop.condition?.value) applyChildren(); break; case 'index': - if (node.children.length){ + if (node.children.length) { recalculateCalculation(prop.condition, actionContext); if (!isFinite(prop.condition?.value)) { actionContext.addLog({ @@ -35,31 +35,31 @@ export default function applyBranch(node, actionContext){ } break; case 'hit': - if (scope['$attackHit']?.value){ - if (!targets.length && !prop.silent) actionContext.addLog({value: '**On hit**'}); + if (scope['$attackHit']) { + if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On hit**' }); applyChildren(); } break; case 'miss': - if (scope['$attackMiss']?.value){ - if (!targets.length && !prop.silent) actionContext.addLog({value: '**On miss**'}); + if (scope['$attackMiss']) { + if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On miss**' }); applyChildren(); } break; case 'failedSave': - if (scope['$saveFailed']?.value){ - if (!targets.length && !prop.silent) actionContext.addLog({value: '**On failed save**'}); + if (scope['$saveFailed']) { + if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On failed save**' }); applyChildren(); } break; case 'successfulSave': - if (scope['$saveSucceeded']?.value){ - if (!targets.length && !prop.silent) actionContext.addLog({value: '**On save**',}); + if (scope['$saveSucceeded']) { + if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On save**', }); applyChildren(); } break; case 'random': - if (node.children.length){ + if (node.children.length) { let index = rollDice(1, node.children.length)[0] - 1; applyNodeTriggers(node, 'after', actionContext); applyProperty(node.children[index], actionContext); diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index 83ad5074..366d62f4 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -27,7 +27,7 @@ export default function applyDamage(node, actionContext) { // Choose target let damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets; // Determine if the hit is critical - let criticalHit = scope['$criticalHit']?.value && + let criticalHit = scope['$criticalHit'] && prop.damageType !== 'healing' // Can't critically heal ; // Double the damage rolls if the hit is critical diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index ca760d7a..f9f01ce5 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -31,8 +31,8 @@ export default function applySavingThrow(node, actionContext) { // If there are no save targets, apply all children as if the save both // succeeeded and failed if (!saveTargets?.length) { - scope['$saveFailed'] = { value: true }; - scope['$saveSucceeded'] = { value: true }; + scope['$saveFailed'] = true; + scope['$saveSucceeded'] = true; applyNodeTriggers(node, 'after', actionContext); return node.children.forEach(child => applyProperty(child, actionContext)); } @@ -90,14 +90,14 @@ export default function applySavingThrow(node, actionContext) { value = values[0]; resultPrefix = `1d20 [ ${value} ] ${rollModifierText}` } - scope['$saveDiceRoll'] = { value }; + scope['$saveDiceRoll'] = value; const result = value + rollModifier || 0; - scope['$saveRoll'] = { value: result }; + scope['$saveRoll'] = result; const saveSuccess = result >= dc; if (saveSuccess) { - scope['$saveSucceeded'] = { value: true }; + scope['$saveSucceeded'] = true; } else { - scope['$saveFailed'] = { value: true }; + scope['$saveFailed'] = true; } if (!prop.silent) actionContext.addLog({ name: saveSuccess ? 'Successful save' : 'Failed save', From 67da6412442a14caf9d2b434a3fee185638f2061 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 31 Jan 2023 20:36:26 +0200 Subject: [PATCH 005/117] Migrated internal variables to ~ prefix --- .../methods/damageProperty.js | 12 ++--- .../applyPropertyByType/applyAction.js | 52 +++++++++---------- .../applyPropertyByType/applyBranch.js | 8 +-- .../applyPropertyByType/applyDamage.js | 8 +-- .../actions/applyPropertyByType/applyRoll.js | 2 +- .../applyPropertyByType/applySavingThrow.js | 20 +++---- app/imports/api/engine/actions/doCastSpell.js | 3 +- app/imports/api/engine/actions/doCheck.js | 10 ++-- .../writeComputation/writeScope.js | 13 +++-- app/imports/constants/VARIABLE_NAME_REGEX.js | 2 +- app/imports/parser/grammar.js | 2 +- app/imports/parser/grammar.ne | 2 +- app/vue.d.ts | 13 +++++ 13 files changed, 82 insertions(+), 65 deletions(-) create mode 100644 app/vue.d.ts diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index f129a562..a8cd0696 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -64,12 +64,12 @@ export function damagePropertyWork({ prop, operation, value, actionContext, logF // Save the value to the scope before applying the before triggers if (operation === 'increment') { if (value >= 0) { - actionContext.scope['$damage'] = value; + actionContext.scope['~damage'] = { value }; } else { - actionContext.scope['$healing'] = -value; + actionContext.scope['~healing'] = { value: -value }; } } else { - actionContext.scope['$set'] = value; + actionContext.scope['~set'] = { value }; } applyTriggers(actionContext.triggers?.damageProperty?.before, prop, actionContext); @@ -77,12 +77,12 @@ export function damagePropertyWork({ prop, operation, value, actionContext, logF // fetch the value from the scope after the before triggers, in case they changed them if (operation === 'increment') { if (value >= 0) { - value = actionContext.scope['$damage']; + value = actionContext.scope['~damage']?.value; } else { - value = -actionContext.scope['$healing']; + value = -actionContext.scope['~healing']?.value; } } else { - value = actionContext.scope['$set']; + value = actionContext.scope['~set']?.value; } let damage, newValue, increment; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index 7587f9cc..ff2f9024 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -51,11 +51,11 @@ export default function applyAction(node, actionContext) { } function applyAttackWithoutTarget({ attack, actionContext }) { - delete actionContext.scope['$attackHit']; - delete actionContext.scope['$attackMiss']; - delete actionContext.scope['$criticalHit']; - delete actionContext.scope['$criticalMiss']; - delete actionContext.scope['$attackRoll']; + delete actionContext.scope['~attackHit']; + delete actionContext.scope['~attackMiss']; + delete actionContext.scope['~criticalHit']; + delete actionContext.scope['~criticalMiss']; + delete actionContext.scope['~attackRoll']; recalculateCalculation(attack, actionContext); const scope = actionContext.scope; @@ -66,16 +66,16 @@ function applyAttackWithoutTarget({ attack, actionContext }) { criticalMiss, } = rollAttack(attack, scope); let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit'; - if (scope['$attackAdvantage'] === 1) { + if (scope['~attackAdvantage']?.value === 1) { name += ' (Advantage)'; - } else if (scope['$attackAdvantage'] === -1) { + } else if (scope['~attackAdvantage']?.value === -1) { name += ' (Disadvantage)'; } if (!criticalMiss) { - scope['$attackHit'] = true + scope['~attackHit'] = { value: true } } if (!criticalHit) { - scope['$attackMiss'] = true; + scope['~attackMiss'] = { value: true }; } actionContext.addLog({ @@ -87,12 +87,12 @@ function applyAttackWithoutTarget({ attack, actionContext }) { function applyAttackToTarget({ attack, target, actionContext }) { const scope = actionContext.scope; - delete scope['$attackHit']; - delete scope['$attackMiss']; - delete scope['$criticalHit']; - delete scope['$criticalMiss']; - delete scope['$attackDiceRoll']; - delete scope['$attackRoll']; + delete scope['~attackHit']; + delete scope['~attackMiss']; + delete scope['~criticalHit']; + delete scope['~criticalMiss']; + delete scope['~attackDiceRoll']; + delete scope['~attackRoll']; recalculateCalculation(attack, actionContext); @@ -109,9 +109,9 @@ function applyAttackToTarget({ attack, target, actionContext }) { let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : result > armor ? 'Hit!' : 'Miss!'; - if (scope['$attackAdvantage'] === 1) { + if (scope['~attackAdvantage']?.value === 1) { name += ' (Advantage)'; - } else if (scope['$attackAdvantage'] === -1) { + } else if (scope['~attackAdvantage']?.value === -1) { name += ' (Disadvantage)'; } @@ -121,9 +121,9 @@ function applyAttackToTarget({ attack, target, actionContext }) { inline: true, }); if (criticalMiss || result < armor) { - scope['$attackMiss'] = true; + scope['~attackMiss'] = { value: true }; } else { - scope['$attackHit'] = true; + scope['~attackHit'] = { value: true }; } } else { actionContext.addLog({ @@ -141,7 +141,7 @@ function applyAttackToTarget({ attack, target, actionContext }) { function rollAttack(attack, scope) { const rollModifierText = numberToSignedString(attack.value, true); let value, resultPrefix; - if (scope['$attackAdvantage'] === 1) { + if (scope['~attackAdvantage']?.value === 1) { const [a, b] = rollDice(2, 20); if (a >= b) { value = a; @@ -150,7 +150,7 @@ function rollAttack(attack, scope) { value = b; resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`; } - } else if (scope['$attackAdvantage'] === -1) { + } else if (scope['~attackAdvantage']?.value === -1) { const [a, b] = rollDice(2, 20); if (a <= b) { value = a; @@ -163,23 +163,23 @@ function rollAttack(attack, scope) { value = rollDice(1, 20)[0]; resultPrefix = `1d20 [${value}] ${rollModifierText}` } - scope['$attackDiceRoll'] = value; + scope['~attackDiceRoll'] = { value }; const result = value + attack.value; - scope['$attackRoll'] = result; + scope['~attackRoll'] = { value: result }; const { criticalHit, criticalMiss } = applyCrits(value, scope); return { resultPrefix, result, value, criticalHit, criticalMiss }; } function applyCrits(value, scope) { - let criticalHitTarget = scope.criticalHitTarget?.value || 20; + const criticalHitTarget = 20; // scope['~criticalHitTarget']?.value || 20; let criticalHit = value >= criticalHitTarget; let criticalMiss; if (criticalHit) { - scope['$criticalHit'] = true; + scope['~criticalHit'] = { value: true }; } else { criticalMiss = value === 1; if (criticalMiss) { - scope['$criticalMiss'] = true; + scope['~criticalMiss'] = { value: true }; } } return { criticalHit, criticalMiss }; diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js index e02ac55a..d48ebc01 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBranch.js @@ -35,25 +35,25 @@ export default function applyBranch(node, actionContext) { } break; case 'hit': - if (scope['$attackHit']) { + if (scope['~attackHit']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On hit**' }); applyChildren(); } break; case 'miss': - if (scope['$attackMiss']) { + if (scope['~attackMiss']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On miss**' }); applyChildren(); } break; case 'failedSave': - if (scope['$saveFailed']) { + if (scope['~saveFailed']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On failed save**' }); applyChildren(); } break; case 'successfulSave': - if (scope['$saveSucceeded']) { + if (scope['~saveSucceeded']?.value) { if (!targets.length && !prop.silent) actionContext.addLog({ value: '**On save**', }); applyChildren(); } diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js index 366d62f4..af6f91ee 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyDamage.js @@ -27,7 +27,7 @@ export default function applyDamage(node, actionContext) { // Choose target let damageTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets; // Determine if the hit is critical - let criticalHit = scope['$criticalHit'] && + let criticalHit = scope['~criticalHit']?.value && prop.damageType !== 'healing' // Can't critically heal ; // Double the damage rolls if the hit is critical @@ -73,12 +73,12 @@ export default function applyDamage(node, actionContext) { damage = Math.floor(damage); // Convert extra damage into the stored type - if (prop.damageType === 'extra' && scope['$lastDamageType']) { - prop.damageType = scope['$lastDamageType']; + if (prop.damageType === 'extra' && scope['~lastDamageType']?.value) { + prop.damageType = scope['~lastDamageType']?.value; } // Store current damage type if (prop.damageType !== 'healing') { - scope['$lastDamageType'] = prop.damageType; + scope['~lastDamageType'] = { value: prop.damageType }; } // Memoise the damage suffix for the log diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js index 2a0a9002..68892231 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyRoll.js @@ -46,7 +46,7 @@ export default function applyRoll(node, actionContext) { } const value = reduced.value; - actionContext.scope[prop.variableName] = value; + actionContext.scope[prop.variableName] = { value }; logValue.push(`**${value}**`); if (!prop.silent) { diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index f9f01ce5..2e2133e8 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -31,18 +31,18 @@ export default function applySavingThrow(node, actionContext) { // If there are no save targets, apply all children as if the save both // succeeeded and failed if (!saveTargets?.length) { - scope['$saveFailed'] = true; - scope['$saveSucceeded'] = true; + scope['~saveFailed'] = { value: true }; + scope['~saveSucceeded'] = { value: true }; applyNodeTriggers(node, 'after', actionContext); return node.children.forEach(child => applyProperty(child, actionContext)); } // Each target makes the saving throw saveTargets.forEach(target => { - delete scope['$saveFailed']; - delete scope['$saveSucceeded']; - delete scope['$saveDiceRoll']; - delete scope['$saveRoll']; + delete scope['~saveFailed']; + delete scope['~saveSucceeded']; + delete scope['~saveDiceRoll']; + delete scope['~saveRoll']; const applyChildren = function () { applyNodeTriggers(node, 'after', actionContext); @@ -90,14 +90,14 @@ export default function applySavingThrow(node, actionContext) { value = values[0]; resultPrefix = `1d20 [ ${value} ] ${rollModifierText}` } - scope['$saveDiceRoll'] = value; + scope['~saveDiceRoll'] = { value }; const result = value + rollModifier || 0; - scope['$saveRoll'] = result; + scope['~saveRoll'] = { value: result }; const saveSuccess = result >= dc; if (saveSuccess) { - scope['$saveSucceeded'] = true; + scope['~saveSucceeded'] = { value: true }; } else { - scope['$saveFailed'] = true; + scope['~saveFailed'] = { value: true }; } if (!prop.silent) actionContext.addLog({ name: saveSuccess ? 'Successful save' : 'Failed save', diff --git a/app/imports/api/engine/actions/doCastSpell.js b/app/imports/api/engine/actions/doCastSpell.js index f512e4ed..4ea03630 100644 --- a/app/imports/api/engine/actions/doCastSpell.js +++ b/app/imports/api/engine/actions/doCastSpell.js @@ -117,7 +117,8 @@ const doAction = new ValidatedMethod({ } } - actionContext.scope['slotLevel'] = slotLevel; + actionContext.scope['slotLevel'] = { value: slotLevel }; + actionContext.scope['~slotLevel'] = { value: slotLevel }; // Do the action doActionWork({ diff --git a/app/imports/api/engine/actions/doCheck.js b/app/imports/api/engine/actions/doCheck.js index 5d9b4164..452c32eb 100644 --- a/app/imports/api/engine/actions/doCheck.js +++ b/app/imports/api/engine/actions/doCheck.js @@ -81,7 +81,7 @@ function rollCheck(prop, actionContext) { rollModifier += effectBonus; let value, values, resultPrefix; - if (scope['$checkAdvantage'] === 1) { + if (scope['~checkAdvantage']?.value === 1) { logName += ' (Advantage)'; const [a, b] = rollDice(2, 20); if (a >= b) { @@ -91,7 +91,7 @@ function rollCheck(prop, actionContext) { value = b; resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `; } - } else if (scope['$checkAdvantage'] === -1) { + } else if (scope['~checkAdvantage']?.value === -1) { logName += ' (Disadvantage)'; const [a, b] = rollDice(2, 20); if (a <= b) { @@ -107,9 +107,9 @@ function rollCheck(prop, actionContext) { resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = ` } const result = (value + rollModifier) || 0; - scope['$checkDiceRoll'] = value; - scope['$checkRoll'] = result; - scope['$checkModifier'] = rollModifier; + scope['~checkDiceRoll'] = { value }; + scope['~checkRoll'] = { value: result }; + scope['~checkModifier'] = { value: rollModifier }; actionContext.addLog({ name: logName, value: `${resultPrefix} **${result}**`, diff --git a/app/imports/api/engine/computation/writeComputation/writeScope.js b/app/imports/api/engine/computation/writeComputation/writeScope.js index d96c2bfe..18d93373 100644 --- a/app/imports/api/engine/computation/writeComputation/writeScope.js +++ b/app/imports/api/engine/computation/writeComputation/writeScope.js @@ -15,18 +15,21 @@ export default function writeScope(creatureId, computation) { let $set, $unset; - for (const key in scope){ + for (const key in scope) { + // Mongo can't handle keys that start with a dollar sign + if (key[0] === '$' || key[0] === '_') continue; + // Remove large properties that aren't likely to be accessed delete scope[key].parent; delete scope[key].ancestors; - + // Remove empty keys for (const subKey in scope[key]) { if (scope[key][subKey] === undefined) { delete scope[key][subKey] } } - + // Only update changed fields if (!EJSON.equals(variables[key], scope[key])) { if (!$set) $set = {}; @@ -53,9 +56,9 @@ export default function writeScope(creatureId, computation) { const update = {}; if ($set) update.$set = $set; if ($unset) update.$unset = $unset; - CreatureVariables.update({_creatureId: creatureId}, update); + CreatureVariables.update({ _creatureId: creatureId }, update); } if (computation.creature?.dirty) { - Creatures.update({_id: creatureId}, {$unset: { dirty: 1 }}); + Creatures.update({ _id: creatureId }, { $unset: { dirty: 1 } }); } } diff --git a/app/imports/constants/VARIABLE_NAME_REGEX.js b/app/imports/constants/VARIABLE_NAME_REGEX.js index 2ff1621f..53671d83 100644 --- a/app/imports/constants/VARIABLE_NAME_REGEX.js +++ b/app/imports/constants/VARIABLE_NAME_REGEX.js @@ -1,4 +1,4 @@ // Must contain a letter, and be made of word characters only -const VARIABLE_NAME_REGEX = /^[a-z$][\w_]*$/i; +const VARIABLE_NAME_REGEX = /^[a-zA-Z~][a-zA-Z_0-9]+$/i; export default VARIABLE_NAME_REGEX; diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js index 43f5b70f..f48896f0 100644 --- a/app/imports/parser/grammar.js +++ b/app/imports/parser/grammar.js @@ -13,7 +13,7 @@ function id(x) { return x[0]; } value: s => s.slice(1, -1).replace('\\n', '\n'), }, name: { - match: /[a-zA-Z_#$]*[a-ce-zA-Z_#$][a-zA-Z0-9_#$]*/, + match: /[a-zA-Z~#][a-zA-Z0-9_]+/, type: moo.keywords({ 'keywords': ['true', 'false'], }), diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne index 3ebdba75..05d2b427 100644 --- a/app/imports/parser/grammar.ne +++ b/app/imports/parser/grammar.ne @@ -11,7 +11,7 @@ value: s => s.slice(1, -1).replace('\\n', '\n'), }, name: { - match: /[a-zA-Z_#$]*[a-ce-zA-Z_#$][a-zA-Z0-9_#$]*/, + match: /[a-zA-Z~#][a-zA-Z0-9_]+/, type: moo.keywords({ 'keywords': ['true', 'false'], }), diff --git a/app/vue.d.ts b/app/vue.d.ts new file mode 100644 index 00000000..d41459e9 --- /dev/null +++ b/app/vue.d.ts @@ -0,0 +1,13 @@ +declare module 'vue/types/options' { + interface ComponentOptions { + meteor?: any; + } +} + +declare module 'vue/types/vue' { + interface Vue { + $subscribe: (name: string, params: any[]) => void; + $autorun: (fn: () => void) => number; + $subReady: Record; + } +} \ No newline at end of file From deca9786b9968966c7c02c2476f97f7f4077d851 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 31 Jan 2023 20:55:35 +0200 Subject: [PATCH 006/117] Fixed grammar to account for d8 not being a name --- app/imports/constants/VARIABLE_NAME_REGEX.js | 2 +- app/imports/parser/grammar.js | 2 +- app/imports/parser/grammar.ne | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/imports/constants/VARIABLE_NAME_REGEX.js b/app/imports/constants/VARIABLE_NAME_REGEX.js index 53671d83..8ea249ca 100644 --- a/app/imports/constants/VARIABLE_NAME_REGEX.js +++ b/app/imports/constants/VARIABLE_NAME_REGEX.js @@ -1,4 +1,4 @@ // Must contain a letter, and be made of word characters only -const VARIABLE_NAME_REGEX = /^[a-zA-Z~][a-zA-Z_0-9]+$/i; +const VARIABLE_NAME_REGEX = /^[~#]?[a-zA-Z]*[a-ce-zA-Z][a-zA-Z0-9_]*$/i; export default VARIABLE_NAME_REGEX; diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js index f48896f0..8f513674 100644 --- a/app/imports/parser/grammar.js +++ b/app/imports/parser/grammar.js @@ -13,7 +13,7 @@ function id(x) { return x[0]; } value: s => s.slice(1, -1).replace('\\n', '\n'), }, name: { - match: /[a-zA-Z~#][a-zA-Z0-9_]+/, + match: /[~#]?[a-zA-Z]*[a-ce-zA-Z][a-zA-Z0-9_]*/, type: moo.keywords({ 'keywords': ['true', 'false'], }), diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne index 05d2b427..e008ac54 100644 --- a/app/imports/parser/grammar.ne +++ b/app/imports/parser/grammar.ne @@ -11,7 +11,7 @@ value: s => s.slice(1, -1).replace('\\n', '\n'), }, name: { - match: /[a-zA-Z~#][a-zA-Z0-9_]+/, + match: /[~#]?[a-zA-Z]*[a-ce-zA-Z][a-zA-Z0-9_]*/, type: moo.keywords({ 'keywords': ['true', 'false'], }), From 2b7851ab32750fbc7d829f8789b5fd5c3661a5a8 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 27 Feb 2023 14:23:50 +0200 Subject: [PATCH 007/117] Added smart-disconnect + connection notifications --- app/.meteor/packages | 1 + app/.meteor/versions | 1 + app/imports/client/ui/layouts/AppLayout.vue | 3 ++ .../client/ui/layouts/ConnectionBanner.vue | 45 +++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 app/imports/client/ui/layouts/ConnectionBanner.vue diff --git a/app/.meteor/packages b/app/.meteor/packages index ff36c66b..ac4c83dd 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -49,3 +49,4 @@ littledata:synced-cron mdg:meteor-apm-agent typescript@4.5.4 seba:minifiers-autoprefixer +mixmax:smart-disconnect diff --git a/app/.meteor/versions b/app/.meteor/versions index 3f632f0b..8f0370a5 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -66,6 +66,7 @@ mikowals:batch-insert@1.3.0 minifier-css@1.6.1 minifier-js@2.7.5 minimongo@1.9.0 +mixmax:smart-disconnect@0.0.5 mobile-experience@1.1.0 mobile-status-bar@1.1.0 modern-browsers@0.1.9 diff --git a/app/imports/client/ui/layouts/AppLayout.vue b/app/imports/client/ui/layouts/AppLayout.vue index a8d3f6bf..4312d456 100644 --- a/app/imports/client/ui/layouts/AppLayout.vue +++ b/app/imports/client/ui/layouts/AppLayout.vue @@ -49,6 +49,7 @@ + @@ -66,12 +67,14 @@ import DialogStack from '/imports/client/ui/dialogStack/DialogStack.vue'; import { mapMutations } from 'vuex'; import SnackbarQueue from '/imports/client/ui/components/snackbars/SnackbarQueue.vue'; import { getUserTier } from '/imports/api/users/patreon/tiers.js'; +import ConnectionBanner from '/imports/client/ui/layouts/ConnectionBanner.vue'; export default { components: { Sidebar, DialogStack, SnackbarQueue, + ConnectionBanner, }, data() { return { diff --git a/app/imports/client/ui/layouts/ConnectionBanner.vue b/app/imports/client/ui/layouts/ConnectionBanner.vue new file mode 100644 index 00000000..ea33b5c3 --- /dev/null +++ b/app/imports/client/ui/layouts/ConnectionBanner.vue @@ -0,0 +1,45 @@ + + + \ No newline at end of file From 50cb6185ce52e6304b32869434ac12a24560c8b0 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 17 Mar 2023 17:45:05 +0200 Subject: [PATCH 008/117] Added proficiency target by tag to backend --- .../buildComputation/linkTypeDependencies.js | 32 +++++++++-- .../computeByType/computeCalculation.js | 54 +++++++++++++++++-- app/imports/api/properties/Proficiencies.js | 50 +++++++++++++++++ 3 files changed, 127 insertions(+), 9 deletions(-) diff --git a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js index 7ed69d19..80d72970 100644 --- a/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/engine/computation/buildComputation/linkTypeDependencies.js @@ -268,13 +268,35 @@ function linkPointBuy(dependencyGraph, prop) { if (prop.inactive) return; } -function linkProficiencies(dependencyGraph, prop) { +function linkProficiencies(dependencyGraph, prop, computation) { // The stats depend on the proficiency if (prop.inactive) return; - prop.stats.forEach(statName => { - if (!statName) return; - dependencyGraph.addLink(statName, prop._id, prop.type); - }); + if (prop.targetByTags) { + getEffectTagTargets(prop, computation).forEach(targetId => { + const targetProp = computation.propsById[targetId]; + if ( + (targetProp.type === 'attribute' || targetProp.type === 'skill') + && targetProp.variableName + && !prop.targetField + ) { + // If the field wasn't specified and we're targeting an attribute or + // skill, just treat it like a normal proficiency on its variable name + dependencyGraph.addLink(targetProp.variableName, prop._id, 'proficiency'); + } else { + // Otherwise target a field on that property + const key = prop.targetField || getDefaultCalculationField(targetProp); + const calcObj = get(targetProp, key); + if (calcObj && calcObj.calculation) { + dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency'); + } + } + }); + } else { + prop.stats.forEach(statName => { + if (!statName) return; + dependencyGraph.addLink(statName, prop._id, 'proficiency'); + }); + } } function linkSavingThrow(dependencyGraph, prop) { diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js index 054fc7f9..3bd59ab4 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeCalculation.js @@ -1,12 +1,13 @@ import evaluateCalculation from '../../utility/evaluateCalculation.js'; -export default function computeCalculation(computation, node){ +export default function computeCalculation(computation, node) { const calcObj = node.data; evaluateCalculation(calcObj, computation.scope); aggregateCalculationEffects(node, computation); + aggregateCalculationProficiencies(node, computation); } -export function aggregateCalculationEffects(node, computation){ +function aggregateCalculationEffects(node, computation) { const calcObj = node.data; delete calcObj.effects; computation.dependencyGraph.forEachLinkedNode( @@ -34,15 +35,60 @@ export function aggregateCalculationEffects(node, computation){ }, true // enumerate only outbound links ); - if (calcObj.effects && typeof calcObj.value === 'number'){ + if (calcObj.effects && typeof calcObj.value === 'number') { calcObj.baseValue = calcObj.value; calcObj.effects.forEach(effect => { if ( effect.operation === 'add' && effect.amount && typeof effect.amount.value === 'number' - ){ + ) { calcObj.value += effect.amount.value } }); } } + +function aggregateCalculationProficiencies(node, computation) { + const calcObj = node.data; + delete calcObj.proficiencies; + delete calcObj.proficiency; + computation.dependencyGraph.forEachLinkedNode( + node.id, + (linkedNode, link) => { + // Only proficiency links + if (link.data !== 'proficiency') return; + // That have data + if (!linkedNode.data) return; + // Ignore inactive props + if (linkedNode.data.inactive) return; + + // Collate effects + calcObj.proficiencies = calcObj.proficiencies || []; + calcObj.proficiencies.push({ + _id: linkedNode.data._id, + name: linkedNode.data.name, + value: linkedNode.data.value, + }); + }, + true // enumerate only outbound links + ); + if (calcObj.proficiencies) { + calcObj.proficiency = 0; + calcObj.proficiencies.forEach(prof => { + if (prof.value > calcObj.proficiency) { + calcObj.proficiency = prof.value; + } + }); + // Get the character's proficiency bonus to apply + let profBonus = computation.scope['proficiencyBonus']?.value || 0; + + // Multiply the proficiency bonus by the actual proficiency + if (calcObj.proficiency === 0.49) { + // Round down proficiency bonus in the special case + calcObj.proficiencyBonus = Math.floor(profBonus * 0.5); + } else { + calcObj.proficiencyBonus = Math.ceil(profBonus * calcObj.proficiency); + } + calcObj.value += calcObj.proficiencyBonus; + } +} diff --git a/app/imports/api/properties/Proficiencies.js b/app/imports/api/properties/Proficiencies.js index e79276ff..8c33fefb 100644 --- a/app/imports/api/properties/Proficiencies.js +++ b/app/imports/api/properties/Proficiencies.js @@ -24,6 +24,56 @@ let ProficiencySchema = new SimpleSchema({ allowedValues: [0.49, 0.5, 1, 2], defaultValue: 1, }, + // True when targeting by tags instead of stats + targetByTags: { + type: Boolean, + optional: true, + }, + // If targeting by tags, the field which will be targeted + targetField: { + type: String, + optional: true, + max: STORAGE_LIMITS.variableName, + }, + // Which tags the effect is applied to + targetTags: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.tagCount, + }, + 'targetTags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, + extraTags: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.extraTagsCount, + }, + 'extraTags.$': { + type: Object, + }, + 'extraTags.$._id': { + type: String, + regEx: SimpleSchema.RegEx.Id, + autoValue() { + if (!this.isSet) return Random.id(); + } + }, + 'extraTags.$.operation': { + type: String, + allowedValues: ['OR', 'NOT'], + defaultValue: 'OR', + }, + 'extraTags.$.tags': { + type: Array, + defaultValue: [], + maxCount: STORAGE_LIMITS.tagCount, + }, + 'extraTags.$.tags.$': { + type: String, + max: STORAGE_LIMITS.tagLength, + }, }); const ComputedOnlyProficiencySchema = new SimpleSchema({}); From 61e72ad874c3a66ea85281879545d08b447b15fb Mon Sep 17 00:00:00 2001 From: Aaron Traas Date: Thu, 23 Mar 2023 11:53:20 -0400 Subject: [PATCH 009/117] Fix link to dicecloud.com The link wasn't working, because the protocol wasn't in the link, therefore Github's Markdown processor was treating it as a relative link. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a50ea99..447d08b4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ DiceCloud ======== -This is the repo for [DiceCloud](dicecloud.com). +This is the repo for [DiceCloud](https://dicecloud.com). DiceCloud is a free, auditable, real-time character sheet for D&D 5e. From 76a0918a78f412f610f371ee3022f8dbf3e5dc17 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 31 Mar 2023 12:19:15 +0200 Subject: [PATCH 010/117] Fixed crash where null ability broke skills --- .../computeVariable/computeVariableAsSkill.js | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js index 01d34101..b13ac43b 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js @@ -1,12 +1,12 @@ import aggregate from './aggregate/index.js'; -export default function computeVariableAsSkill(computation, node, prop){ +export default function computeVariableAsSkill(computation, node, prop) { // Skills are based on some ability Modifier let ability = computation.scope[prop.ability]; prop.abilityMod = ability?.modifier || 0; // Inherit the ability's skill effects and proficiencies if skill is not a save - if (prop.skillType !== 'save' && ability){ + if (prop.skillType !== 'save' && ability) { aggregateAbilityEffects({ computation, skillNode: node, @@ -21,7 +21,7 @@ export default function computeVariableAsSkill(computation, node, prop){ let profBonus = computation.scope['proficiencyBonus']?.value || 0; // Multiply the proficiency bonus by the actual proficiency - if(prop.proficiency === 0.49){ + if (prop.proficiency === 0.49) { // Round down proficiency bonus in the special case profBonus = Math.floor(profBonus * 0.5); } else { @@ -37,7 +37,7 @@ export default function computeVariableAsSkill(computation, node, prop){ prop.effects = node.data.effects; // If there is no aggregator, determine if the prop can hide, then exit - if (!aggregator){ + if (!aggregator) { prop.hide = statBase === undefined && prop.proficiency == 0 || undefined; @@ -52,14 +52,14 @@ export default function computeVariableAsSkill(computation, node, prop){ if (aggregator.set !== undefined) { result = aggregator.set; } - if (Number.isFinite(result)){ + if (Number.isFinite(result)) { result = Math.floor(result); } prop.value = result; // Advantage/disadvantage - if (aggregator.advantage && !aggregator.disadvantage){ + if (aggregator.advantage && !aggregator.disadvantage) { prop.advantage = 1; - } else if (aggregator.disadvantage && !aggregator.advantage){ + } else if (aggregator.disadvantage && !aggregator.advantage) { prop.advantage = -1; } else { prop.advantage = 0; @@ -76,7 +76,8 @@ export default function computeVariableAsSkill(computation, node, prop){ prop.rollBonuses = aggregator.rollBonus; } -function aggregateAbilityEffects({computation, skillNode, abilityNode}){ +function aggregateAbilityEffects({ computation, skillNode, abilityNode }) { + if (!abilityNode?.id) return; computation.dependencyGraph.forEachLinkedNode( abilityNode.id, (linkedNode, link) => { @@ -85,15 +86,15 @@ function aggregateAbilityEffects({computation, skillNode, abilityNode}){ if (linkedNode.data.inactive) return; // Check that the link is a valid effect/proficiency to pass on // to a skill from its ability - if (link.data === 'effect'){ + if (link.data === 'effect') { if (![ 'advantage', 'disadvantage', 'passiveAdd', 'fail', 'conditional' - ].includes(linkedNode.data.operation)){ + ].includes(linkedNode.data.operation)) { return; } } // Apply the aggregations - let arg = {node: skillNode, linkedNode, link}; + let arg = { node: skillNode, linkedNode, link }; aggregate.effect(arg); aggregate.proficiency(arg); }, From 8e610c2cd87a48cc06fb39e2ffc3a3a44a1e0a79 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 31 Mar 2023 12:21:30 +0200 Subject: [PATCH 011/117] Slot fill fields given to all lib nodes --- app/imports/api/library/LibraryNodes.js | 54 +++- app/imports/api/properties/SlotFillers.js | 18 -- .../client/ui/properties/PropertyForm.vue | 119 +++++++++ app/jsconfig.json | 3 +- app/package-lock.json | 237 +++++++++++------- app/package.json | 25 +- app/vue.d.ts | 13 - 7 files changed, 336 insertions(+), 133 deletions(-) create mode 100644 app/imports/client/ui/properties/PropertyForm.vue delete mode 100644 app/vue.d.ts diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index 50d97cd3..9c271c4e 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -36,6 +36,27 @@ let LibraryNodeSchema = new SimpleSchema({ type: String, max: STORAGE_LIMITS.tagLength, }, + icon: { + type: storedIconsSchema, + optional: true, + max: STORAGE_LIMITS.icon, + }, + + // Library-specific properties, these can be stripped from the resulting + // creature properties + + // Will this property show up in the slot-fill dialog + fillSlots: { + type: Boolean, + optional: true, + index: 1, + }, + // Will this property show up in the insert-from-library dialog + searchable: { + type: Boolean, + optional: true, + index: 1, + }, libraryTags: { type: Array, defaultValue: [], @@ -45,11 +66,36 @@ let LibraryNodeSchema = new SimpleSchema({ type: String, max: STORAGE_LIMITS.tagLength, }, - icon: { - type: storedIconsSchema, + // Overrides the type when searching for properties + slotFillerType: { + type: String, optional: true, - max: STORAGE_LIMITS.icon, - } + max: STORAGE_LIMITS.variableName, + }, + // Image to display when filling the slot + slotFillImage: { + type: String, + optional: true, + max: STORAGE_LIMITS.url, + }, + // Fill more than one quantity in a slot, like feats and ability score + // improvements, filtered out of UI if there isn't space in quantityExpected + slotQuantityFilled: { + type: SimpleSchema.Integer, + optional: true, // Undefined implies 1 + }, + // Filters out of UI if condition isn't met, but isn't otherwise enforced + slotFillerCondition: { + type: String, + optional: true, + max: STORAGE_LIMITS.calculation, + }, + // Text to display if slot filler condition fails + slotFillerConditionNote: { + type: String, + optional: true, + max: STORAGE_LIMITS.calculation, + }, }); // Set up server side search index diff --git a/app/imports/api/properties/SlotFillers.js b/app/imports/api/properties/SlotFillers.js index 185e7ba2..42a2f820 100644 --- a/app/imports/api/properties/SlotFillers.js +++ b/app/imports/api/properties/SlotFillers.js @@ -19,24 +19,6 @@ let SlotFillerSchema = new SimpleSchema({ optional: true, max: STORAGE_LIMITS.description, }, - // Overrides the type when searching for properties - slotFillerType: { - type: String, - optional: true, - max: STORAGE_LIMITS.variableName, - }, - // Fill more than one quantity in a slot, like feats and ability score - // improvements, filtered out of UI if there isn't space in quantityExpected - slotQuantityFilled: { - type: SimpleSchema.Integer, - defaultValue: 1, - }, - // Filters out of UI if condition isn't met, but isn't otherwise enforced - slotFillerCondition: { - type: String, - optional: true, - max: STORAGE_LIMITS.calculation, - }, }); const ComputedOnlySlotFillerSchema = new SimpleSchema({}); diff --git a/app/imports/client/ui/properties/PropertyForm.vue b/app/imports/client/ui/properties/PropertyForm.vue new file mode 100644 index 00000000..9882cf6d --- /dev/null +++ b/app/imports/client/ui/properties/PropertyForm.vue @@ -0,0 +1,119 @@ + + + \ No newline at end of file diff --git a/app/jsconfig.json b/app/jsconfig.json index e4b479a3..6d80377f 100644 --- a/app/jsconfig.json +++ b/app/jsconfig.json @@ -19,7 +19,8 @@ "allowJs": true }, "vueCompilerOptions": { - "target": 2 + "target": 2.7, + // "target": 2, // For Vue version <= 2.6.14 }, "exclude": [ "node_modules", diff --git a/app/package-lock.json b/app/package-lock.json index cae51f46..4abbccd7 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -50,15 +50,15 @@ } }, "@babel/parser": { - "version": "7.20.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", - "integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", "dev": true }, "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", + "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -185,15 +185,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.44.0.tgz", - "integrity": "sha512-j5ULd7FmmekcyWeArx+i8x7sdRHzAtXTkmDPthE4amxZOWKFK7bomoJ4r7PJ8K7PoMzD16U8MmuZFAonr1ERvw==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.0.tgz", + "integrity": "sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/type-utils": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "@typescript-eslint/scope-manager": "5.54.0", + "@typescript-eslint/type-utils": "5.54.0", + "@typescript-eslint/utils": "5.54.0", "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "regexpp": "^3.2.0", @@ -222,14 +223,14 @@ } }, "@typescript-eslint/parser": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.44.0.tgz", - "integrity": "sha512-H7LCqbZnKqkkgQHaKLGC6KUjt3pjJDx8ETDqmwncyb6PuoigYajyAwBGz08VU/l86dZWZgI4zm5k2VaKqayYyA==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.0.tgz", + "integrity": "sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/scope-manager": "5.54.0", + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/typescript-estree": "5.54.0", "debug": "^4.3.4" }, "dependencies": { @@ -245,23 +246,23 @@ } }, "@typescript-eslint/scope-manager": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.44.0.tgz", - "integrity": "sha512-2pKml57KusI0LAhgLKae9kwWeITZ7IsZs77YxyNyIVOwQ1kToyXRaJLl+uDEXzMN5hnobKUOo2gKntK9H1YL8g==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz", + "integrity": "sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/visitor-keys": "5.44.0" + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/visitor-keys": "5.54.0" } }, "@typescript-eslint/type-utils": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.44.0.tgz", - "integrity": "sha512-A1u0Yo5wZxkXPQ7/noGkRhV4J9opcymcr31XQtOzcc5nO/IHN2E2TPMECKWYpM3e6olWEM63fq/BaL1wEYnt/w==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.0.tgz", + "integrity": "sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.44.0", - "@typescript-eslint/utils": "5.44.0", + "@typescript-eslint/typescript-estree": "5.54.0", + "@typescript-eslint/utils": "5.54.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -278,19 +279,19 @@ } }, "@typescript-eslint/types": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.44.0.tgz", - "integrity": "sha512-Tp+zDnHmGk4qKR1l+Y1rBvpjpm5tGXX339eAlRBDg+kgZkz9Bw+pqi4dyseOZMsGuSH69fYfPJCBKBrbPCxYFQ==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.0.tgz", + "integrity": "sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.44.0.tgz", - "integrity": "sha512-M6Jr+RM7M5zeRj2maSfsZK2660HKAJawv4Ud0xT+yauyvgrsHu276VtXlKDFnEmhG+nVEd0fYZNXGoAgxwDWJw==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.0.tgz", + "integrity": "sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/visitor-keys": "5.44.0", + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/visitor-keys": "5.54.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -319,16 +320,16 @@ } }, "@typescript-eslint/utils": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.44.0.tgz", - "integrity": "sha512-fMzA8LLQ189gaBjS0MZszw5HBdZgVwxVFShCO3QN+ws3GlPkcy9YuS3U4wkT6su0w+Byjq3mS3uamy9HE4Yfjw==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.0.tgz", + "integrity": "sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.44.0", - "@typescript-eslint/types": "5.44.0", - "@typescript-eslint/typescript-estree": "5.44.0", + "@typescript-eslint/scope-manager": "5.54.0", + "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/typescript-estree": "5.54.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -355,12 +356,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.44.0.tgz", - "integrity": "sha512-a48tLG8/4m62gPFbJ27FxwCOqPKxsb8KC3HkmYoq2As/4YyjQl1jDbRr1s63+g4FS/iIehjmN3L5UjmKva1HzQ==", + "version": "5.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.0.tgz", + "integrity": "sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.44.0", + "@typescript-eslint/types": "5.54.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { @@ -373,31 +374,85 @@ } }, "@vue/compiler-core": { - "version": "3.2.45", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz", - "integrity": "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==", + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz", + "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==", "dev": true, "requires": { "@babel/parser": "^7.16.4", - "@vue/shared": "3.2.45", + "@vue/shared": "3.2.47", "estree-walker": "^2.0.2", "source-map": "^0.6.1" } }, "@vue/compiler-dom": { - "version": "3.2.45", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz", - "integrity": "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==", + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz", + "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==", "dev": true, "requires": { - "@vue/compiler-core": "3.2.45", - "@vue/shared": "3.2.45" + "@vue/compiler-core": "3.2.47", + "@vue/shared": "3.2.47" + } + }, + "@vue/reactivity": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz", + "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==", + "dev": true, + "requires": { + "@vue/shared": "3.2.47" + }, + "dependencies": { + "@vue/shared": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", + "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==", + "dev": true + } + } + }, + "@vue/runtime-core": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz", + "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==", + "dev": true, + "requires": { + "@vue/reactivity": "3.2.47", + "@vue/shared": "3.2.47" + }, + "dependencies": { + "@vue/shared": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", + "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==", + "dev": true + } + } + }, + "@vue/runtime-dom": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz", + "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==", + "dev": true, + "requires": { + "@vue/runtime-core": "3.2.47", + "@vue/shared": "3.2.47", + "csstype": "^2.6.8" + }, + "dependencies": { + "@vue/shared": { + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", + "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==", + "dev": true + } } }, "@vue/shared": { - "version": "3.2.45", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz", - "integrity": "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==", + "version": "3.2.47", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz", + "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==", "dev": true }, "abbrev": { @@ -530,9 +585,9 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" }, "aws-sdk": { - "version": "2.1262.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1262.0.tgz", - "integrity": "sha512-XbaK/XUIxwLEBnHANhJ0RTZtiU288lFRj5FllSihQ5Kb0fibKyW8kJFPsY+NzzDezLH5D3WdGbTKb9fycn5TbA==", + "version": "2.1326.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1326.0.tgz", + "integrity": "sha512-LSGiO4RSooupHnkvYbPOuOYqwAxmcnYinwIxBz4P1YI8ulhZZ/pypOj/HKqC629UyhY1ndSMtlM1l56U74UclA==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -875,6 +930,12 @@ "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==" }, + "csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -986,9 +1047,9 @@ } }, "dompurify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", - "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==" + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.5.tgz", + "integrity": "sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA==" }, "ecc-jsbn": { "version": "0.1.2", @@ -1299,9 +1360,9 @@ "dev": true }, "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -1416,9 +1477,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -1491,6 +1552,12 @@ "get-intrinsic": "^1.1.3" } }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1575,9 +1642,9 @@ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz", - "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==" + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" }, "ignore-styles": { "version": "5.0.1", @@ -1850,9 +1917,9 @@ } }, "marked": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.3.tgz", - "integrity": "sha512-slWRdJkbTZ+PjkyJnE30Uid64eHwbwa1Q25INCAYfZlK4o6ylagBy/Le9eWntqJFoFT93ikUKMv47GZ4gTwHkw==" + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz", + "integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==" }, "merge2": { "version": "1.4.1", @@ -2920,9 +2987,9 @@ "dev": true }, "pretty-bytes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.0.0.tgz", - "integrity": "sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.0.tgz", + "integrity": "sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==" }, "prism-media": { "version": "1.3.1", @@ -3549,9 +3616,9 @@ "dev": true }, "typescript": { - "version": "4.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", - "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "uri-js": { @@ -3688,9 +3755,9 @@ } }, "vuetify": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.12.tgz", - "integrity": "sha512-qe3hcMpWmT1O15tp+p65lOS7UKZ/hQYQktCsw9iXx2u3RwVbX6GR82gY2iROrKsiAzYDvMgrYxWQwY/pUfkekw==" + "version": "2.6.14", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.14.tgz", + "integrity": "sha512-nr6wU3uTzhhEPssH23cW0Ee/hCrayp7cjl3nNjM2OmNwiJlV91tZiL1VO3597SqZyjh1xIa+m9J2rpKTSdIlrA==" }, "vuetify-upload-button": { "version": "2.0.2", diff --git a/app/package.json b/app/package.json index 7cee6eb3..8e4ea36f 100644 --- a/app/package.json +++ b/app/package.json @@ -20,28 +20,28 @@ "npm": "6.13.x" }, "dependencies": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.21.0", "@chenfengyuan/vue-countdown": "^1.1.5", "@tozd/vue-observer-utils": "^0.5.0", - "aws-sdk": "^2.1262.0", + "aws-sdk": "^2.1326.0", "bcrypt": "^5.1.0", "chroma-js": "^2.4.2", "css-box-shadow": "^1.0.0-3", "date-fns": "^1.30.1", "ddp-rate-limiter-mixin": "^1.1.10", "discord.js": "^12.5.3", - "dompurify": "^2.4.1", - "ignore": "^5.2.1", + "dompurify": "^2.4.5", + "ignore": "^5.2.4", "ignore-styles": "^5.0.1", "lodash": "^4.17.20", - "marked": "^4.2.3", + "marked": "^4.2.12", "meteor-node-stubs": "^1.2.5", "minify-css-string": "^1.0.0", "moo": "^0.5.2", "nearley": "^2.19.1", "ngraph.graph": "^19.1.0", "ngraph.path": "^1.4.0", - "pretty-bytes": "^6.0.0", + "pretty-bytes": "^6.1.0", "qrcode.vue": "^1.7.0", "request": "^2.88.2", "sharp": "^0.30.7", @@ -53,19 +53,20 @@ "vue-reactive-provide": "^0.3.0", "vue-router": "^3.6.5", "vuedraggable": "^2.23.2", - "vuetify": "^2.6.12", + "vuetify": "^2.6.14", "vuetify-upload-button": "^2.0.2", "vuex": "^3.1.3" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^5.44.0", - "@typescript-eslint/parser": "^5.44.0", - "@vue/compiler-dom": "^3.2.45", + "@typescript-eslint/eslint-plugin": "^5.54.0", + "@typescript-eslint/parser": "^5.54.0", + "@vue/compiler-dom": "^3.2.47", + "@vue/runtime-dom": "^3.2.47", "chai": "^4.3.7", "eslint": "^7.32.0", "eslint-plugin-vue": "^7.20.0", "eslint-plugin-vuetify": "^1.1.0", - "typescript": "^4.9.3" + "typescript": "^4.9.5" }, "eslintConfig": { "extends": [ @@ -118,4 +119,4 @@ ] } } -} \ No newline at end of file +} diff --git a/app/vue.d.ts b/app/vue.d.ts deleted file mode 100644 index d41459e9..00000000 --- a/app/vue.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -declare module 'vue/types/options' { - interface ComponentOptions { - meteor?: any; - } -} - -declare module 'vue/types/vue' { - interface Vue { - $subscribe: (name: string, params: any[]) => void; - $autorun: (fn: () => void) => number; - $subReady: Record; - } -} \ No newline at end of file From b9588c83b16bc5ee4a1ab98605e6e399b256ff6c Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Sat, 1 Apr 2023 11:27:52 +0200 Subject: [PATCH 012/117] Added dice functions to parse engine --- app/imports/parser/functions.js | 203 ++++++++++++++++++---- app/imports/parser/parseTree/call.js | 64 ++++--- app/imports/parser/parseTree/rollArray.js | 20 ++- 3 files changed, 223 insertions(+), 64 deletions(-) diff --git a/app/imports/parser/functions.js b/app/imports/parser/functions.js index 90a7e8df..9482fd9b 100644 --- a/app/imports/parser/functions.js +++ b/app/imports/parser/functions.js @@ -1,11 +1,12 @@ import resolve from '/imports/parser/resolve.js' +import rollDice from '/imports/parser/rollDice.js'; export default { 'abs': { comment: 'Returns the absolute value of a number', examples: [ - {input: 'abs(9)', result: '9'}, - {input: 'abs(-3)', result: '3'}, + { input: 'abs(9)', result: '9' }, + { input: 'abs(-3)', result: '3' }, ], arguments: ['number'], resultType: 'number', @@ -14,8 +15,8 @@ export default { 'sqrt': { comment: 'Returns the square root of a number', examples: [ - {input: 'sqrt(16)', result: '4'}, - {input: 'sqrt(10)', result: '3.1622776601683795'}, + { input: 'sqrt(16)', result: '4' }, + { input: 'sqrt(10)', result: '3.1622776601683795' }, ], arguments: ['number'], resultType: 'number', @@ -23,14 +24,14 @@ export default { }, 'max': { comment: 'Returns the largest of the given numbers', - examples: [{input: 'max(12, 6, 3, 168)', result: '168'}], + examples: [{ input: 'max(12, 6, 3, 168)', result: '168' }], arguments: anyNumberOf('number'), resultType: 'number', fn: Math.max, }, 'min': { comment: 'Returns the smallest of the given numbers', - examples: [{input: 'min(12, 6, 3, 168)', result: '3'}], + examples: [{ input: 'min(12, 6, 3, 168)', result: '3' }], arguments: anyNumberOf('number'), resultType: 'number', fn: Math.min, @@ -38,9 +39,9 @@ export default { 'round': { comment: 'Returns the value of a number rounded to the nearest integer', examples: [ - {input: 'round(5.95)', result: '6'}, - {input: 'round(5.5)', result: '6'}, - {input: 'round(5.05)', result: '5'}, + { input: 'round(5.95)', result: '6' }, + { input: 'round(5.5)', result: '6' }, + { input: 'round(5.05)', result: '5' }, ], arguments: ['number'], resultType: 'number', @@ -49,10 +50,10 @@ export default { 'floor': { comment: 'Rounds a number down to the next smallest integer', examples: [ - {input: 'floor(5.95)', result: '5'}, - {input: 'floor(5.05)', result: '5'}, - {input: 'floor(5)', result: '5'}, - {input: 'floor(-5.5)', result: '-6'}, + { input: 'floor(5.95)', result: '5' }, + { input: 'floor(5.05)', result: '5' }, + { input: 'floor(5)', result: '5' }, + { input: 'floor(-5.5)', result: '-6' }, ], arguments: ['number'], resultType: 'number', @@ -61,10 +62,10 @@ export default { 'ceil': { comment: 'Rounds a number up to the next largest integer', examples: [ - {input: 'ceil(5.95)', result: '6'}, - {input: 'ceil(5.05)', result: '6'}, - {input: 'ceil(5)', result: '5'}, - {input: 'ceil(-5.5)', result: '-5'}, + { input: 'ceil(5.95)', result: '6' }, + { input: 'ceil(5.05)', result: '6' }, + { input: 'ceil(5)', result: '5' }, + { input: 'ceil(-5.5)', result: '-5' }, ], arguments: ['number'], resultType: 'number', @@ -73,21 +74,21 @@ export default { 'trunc': { comment: 'Returns the integer part of a number by removing any fractional digits', examples: [ - {input: 'trunc(5.95)', result: '5'}, - {input: 'trunc(5.05)', result: '5'}, - {input: 'trunc(5)', result: '5'}, - {input: 'trunc(-5.5)', result: '-5'}, + { input: 'trunc(5.95)', result: '5' }, + { input: 'trunc(5.05)', result: '5' }, + { input: 'trunc(5)', result: '5' }, + { input: 'trunc(-5.5)', result: '-5' }, ], - arguments:[ 'number'], + arguments: ['number'], resultType: 'number', fn: Math.trunc, }, 'sign': { comment: 'Returns either a positive or negative 1, indicating the sign of a number, or zero', examples: [ - {input: 'sign(-3)', result: '-1'}, - {input: 'sign(3)', result: '1'}, - {input: 'sign(0)', result: '0'}, + { input: 'sign(-3)', result: '-1' }, + { input: 'sign(3)', result: '1' }, + { input: 'sign(0)', result: '0' }, ], arguments: ['number'], resultType: 'number', @@ -96,15 +97,15 @@ export default { 'tableLookup': { comment: 'Returns the index of the last value in the array that is less than the specified amount', examples: [ - {input: 'tableLookup([100, 300, 900], 457)', result: '2'}, - {input: 'tableLookup([100, 300, 900], 23)', result: '0'}, - {input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3'}, - {input: 'tableLookup([100, 300], 594)', result: '2'}, + { input: 'tableLookup([100, 300, 900], 457)', result: '2' }, + { input: 'tableLookup([100, 300, 900], 23)', result: '0' }, + { input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3' }, + { input: 'tableLookup([100, 300], 594)', result: '2' }, ], arguments: ['array', 'number'], resultType: 'number', - fn: function tableLookup(arrayNode, number){ - for(let i in arrayNode.values){ + fn: function tableLookup(arrayNode, number) { + for (let i in arrayNode.values) { let node = arrayNode.values[i]; if (node.value > number) return +i; } @@ -114,18 +115,146 @@ export default { 'resolve': { comment: 'Forces the given calcultion to resolve into a number, even in calculations where it would usually keep the unknown values as is', examples: [ - {input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7'}, - {input: 'resolve(1d6)', result: '4'}, + { input: 'resolve(someUndefinedVariable + 3 + 4)', result: '7' }, + { input: 'resolve(1d6)', result: '4' }, ], arguments: ['parseNode'], - fn: function resolveFn(node){ - let {result} = resolve('reduce', node, this.scope, this.context); + fn: function resolveFn(node) { + let { result } = resolve('reduce', node, this.scope, this.context); return result; } - } + }, + 'dropLowest': { + comment: 'Removes one or more of the lowest values in a roll', + examples: [ + ], + arguments: ['rollArray', 'number'], + maxResolveLevels: ['roll', 'reduce'], + minArguments: 1, + maxArguments: 2, + resultType: 'rollArray', + fn: function dropLowestFn(rollArray, numberToDrop = 1) { + // Create a new array where the values are sorted in ascending order + const sortedArray = [...rollArray.values].sort(function (a, b) { + return a.value - b.value; + }); + + // mark the N smallest elements as dropped + for (let i = 0; i < numberToDrop; i += 1) { + console.log('dropped ' + sortedArray[i].value); + sortedArray[i].disabled = true; + sortedArray[i].disabledBy = 'dropLowest'; + } + return rollArray; + }, + }, + 'dropHighest': { + comment: 'Removes one or more of the highest values in a roll', + examples: [ + ], + arguments: ['rollArray', 'number'], + maxResolveLevels: ['roll', 'reduce'], + minArguments: 1, + maxArguments: 2, + resultType: 'rollArray', + fn: function dropHighestFn(rollArray, numberToDrop = 1) { + // Create a new array where the values are sorted in ascending order + const sortedArray = [...rollArray.values].sort(function (a, b) { + return b.value - a.value; + }); + + // mark the N smallest elements as dropped + for (let i = 0; i < numberToDrop; i += 1) { + sortedArray[i].disabled = true; + sortedArray[i].disabledBy = 'dropHighest'; + } + return rollArray; + }, + }, + 'reroll': { + comment: 'Rerolls if a number is less than or equal to the given value', + examples: [ + ], + arguments: ['rollArray', 'number', 'boolean'], + maxResolveLevels: ['roll', 'reduce'], + minArguments: 1, + maxArguments: 3, + resultType: 'rollArray', + fn: function rerollFn(rollArray, numberToReroll = 1, keepNewRoll = false) { + let rollValues = rollArray.values + // Iterate through the roll values + for (let i = 0; i < rollValues.length; i += 1) { + // If the number is less than the reroll limit + if (rollValues[i].value <= numberToReroll) { + // Disable it + rollValues[i].disabled = true; + rollValues[i].disabledBy = 'reroll'; + // Roll it again, insert the new roll into the list at the next index + rollValues.splice(i + 1, 0, { + value: rollDice(1, rollArray.diceSize)[0], + }); + // Skip iterating the inserted roll if we are forced to keep it + if (keepNewRoll) { + i += 1; + } + } + if (i >= 100) { + this.context.error('Can\'t roll more than 100 dice at once'); + return rollArray; + } + } + return rollArray; + }, + }, + 'explode': { + comment: 'Rerolls if a number is greater than or equal to the given value', + examples: [ + ], + arguments: ['rollArray', 'number', 'number'], + maxResolveLevels: ['roll', 'reduce', 'reduce'], + minArguments: 1, + maxArguments: 3, + resultType: 'rollArray', + fn: function explodeFn(rollArray, depth = 1, numberToReroll = rollArray.diceSize) { + let overflowErrored = false; + if (depth > 99) depth = 99; + let rollValues = rollArray.values + // Iterate through the roll values + for (let i = 0; i < rollValues.length; i += 1) { + // If the number is greater than or equal to the reroll limit + // And there is space to reroll it + if (rollValues[i].value >= numberToReroll) { + rollValues[i].bold = true; + let explodeDepth = 1; + let explodeRoll; + do { + // Before inserting this roll, make sure the total dice in the roll + // Doesn't exceed 100 + if (rollValues.length >= 100) { + if (!overflowErrored) { + this.context.error('Can\'t roll more than 100 dice at once'); + overflowErrored = true; + } + break; + } + explodeDepth += 1; + explodeRoll = rollDice(1, rollArray.diceSize)[0]; + const rollObj = { + value: explodeRoll, + italics: true, + }; + // Insert the roll + rollValues.splice(i + 1, 0, rollObj); + i += 1; + } while (explodeDepth <= depth && explodeRoll >= numberToReroll) + } + } + return rollArray; + }, + }, } -function anyNumberOf(type){ +function anyNumberOf(type) { let argumentArray = [type]; argumentArray.anyLength = true; return argumentArray; diff --git a/app/imports/parser/parseTree/call.js b/app/imports/parser/parseTree/call.js index 658c5f49..24b7d3c9 100644 --- a/app/imports/parser/parseTree/call.js +++ b/app/imports/parser/parseTree/call.js @@ -4,14 +4,14 @@ import functions from '/imports/parser/functions.js'; import resolve, { toString, traverse, map } from '../resolve.js'; const call = { - create({functionName, args}) { + create({ functionName, args }) { return { parseType: 'call', functionName, args, } }, - resolve(fn, node, scope, context){ + resolve(fn, node, scope, context) { let func = functions[node.functionName]; // Check that the function exists if (!func) { @@ -25,9 +25,22 @@ const call = { }; } + // Resolve a given node to a maximum depth of resolution + const resolveToLevel = (node, maxResolveFn = 'reduce') => { + // Determine the actual depth to resolve to + let resolveFn = 'reduce'; + if (fn === 'compile' || maxResolveFn === 'compile') { + resolveFn = 'compile'; + } else if (fn === 'roll' || maxResolveFn === 'roll') { + resolveFn = 'roll'; + } + // Resolve + return resolve(resolveFn, node, scope, context); + } + // Resolve the arguments - let resolvedArgs = node.args.map(arg => { - let { result } = resolve(fn, arg, scope, context); + let resolvedArgs = node.args.map((arg, i) => { + let { result } = resolveToLevel(arg, func.maxResolveLevels?.[i]); return result; }); @@ -36,12 +49,12 @@ const call = { node, fn, resolvedArgs, - argumentsExpected: func.arguments, + func, context, }); - if (checkFailed){ - if (fn === 'reduce'){ + if (checkFailed) { + if (fn === 'reduce') { context.error(`Invalid arguments to ${node.functionName} function`); return { result: error.create({ @@ -66,7 +79,7 @@ const call = { if ( arg.parseType === 'constant' && func.arguments[index] !== 'parseNode' - ){ + ) { return arg.value; } else { return arg; @@ -75,20 +88,21 @@ const call = { try { // Run the function - let value = func.fn.apply({scope, context}, mappedArgs); + let value = func.fn.apply({ + scope, + context, + }, mappedArgs); let valueType = typeof value; - if (valueType === 'number' || valueType === 'string' || valueType === 'boolean'){ + if (valueType === 'number' || valueType === 'string' || valueType === 'boolean') { // Convert constant results into constant nodes return { - result: constant.create({ value, valueType }), + result: constant.create({ value }), context, }; } else { - return { - result: value, - context, - }; + // Resolve the return value + return resolve(fn, value, scope, context); } } catch (error) { context.error(error.message || error); @@ -101,26 +115,28 @@ const call = { } } }, - toString(node){ + toString(node) { return `${node.functionName}(${node.args.map(arg => toString(arg)).join(', ')})`; }, - traverse(node, fn){ + traverse(node, fn) { fn(node); node.args.forEach(arg => traverse(arg, fn)); }, - map(node, fn){ + map(node, fn) { const resultingNode = fn(node); - if (resultingNode === node){ + if (resultingNode === node) { node.args = node.args.map(arg => map(arg, fn)); } return resultingNode; }, - checkArugments({node, fn, argumentsExpected, resolvedArgs, context}){ + checkArugments({ node, fn, func, resolvedArgs, context }) { + const argumentsExpected = func.arguments; // Check that the number of arguments matches the number expected if ( !argumentsExpected.anyLength && - argumentsExpected.length !== resolvedArgs.length - ){ + resolvedArgs.length > (func.maxArguments ?? argumentsExpected.length) || + resolvedArgs.length < (func.minArguments ?? argumentsExpected.length) + ) { context.error('Incorrect number of arguments ' + `to ${node.functionName} function, ` + `expected ${argumentsExpected.length} got ${resolvedArgs.length}`); @@ -131,14 +147,14 @@ const call = { // Check that each argument is of the correct type resolvedArgs.forEach((node, index) => { let type; - if (argumentsExpected.anyLength){ + if (argumentsExpected.anyLength) { type = argumentsExpected[0]; } else { type = argumentsExpected[index]; } if (type === 'parseNode') return; if (node.parseType !== type && node.valueType !== type) failed = true; - if (failed && fn === 'reduce'){ + if (failed && fn === 'reduce') { let typeName = typeof type === 'string' ? type : type.constructor.name; let nodeName = node.parseType; context.error(`Incorrect arguments to ${node.functionName} function` + diff --git a/app/imports/parser/parseTree/rollArray.js b/app/imports/parser/parseTree/rollArray.js index 969b948d..30f37240 100644 --- a/app/imports/parser/parseTree/rollArray.js +++ b/app/imports/parser/parseTree/rollArray.js @@ -4,7 +4,7 @@ const rollArray = { create({ values, diceSize, diceNum }) { return { parseType: 'rollArray', - values, + values: values.map(v => ({ value: v })), diceSize, diceNum, }; @@ -16,10 +16,13 @@ const rollArray = { }; }, toString(node) { - return `${node.diceNum || ''}d${node.diceSize} [ ${node.values.join(', ')} ]`; + return `${node.diceNum || ''}d${node.diceSize} [${valuesToString(node.values)}]`; }, reduce(node, scope, context) { - const total = node.values.reduce((a, b) => a + b, 0); + const total = node.values.reduce((a, b) => { + if (b.disabled) return a; + return a + b.value; + }, 0); return { result: constant.create({ value: total, @@ -29,4 +32,15 @@ const rollArray = { }, } +function valuesToString(values) { + return values.map(v => { + let text = `${v.value}`; + if (v.disabled) text = `~~${text}~~`; + if (v.italics) text = `*${text}*`; + if (v.bold) text = `**${text}**`; + if (v.underline) text = `__${text}__`; + return text; + }).join(', '); +} + export default rollArray; From 53e88af93a319bf3542893e8a65ed57b0d155c7f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 3 Apr 2023 16:35:29 +0200 Subject: [PATCH 013/117] Worked on universal property form --- .../client/ui/components/ColorPicker.vue | 16 ++- .../ui/components/global/IconPicker.vue | 29 ++++- .../client/ui/components/propertyToolbar.vue | 5 - .../CreaturePropertyDialog.vue | 32 +----- .../client/ui/properties/PropertyForm.vue | 104 ++++++++++++------ .../components/attributes/AttributeCard.vue | 8 ++ .../components/attributes/ResourceCard.vue | 10 ++ .../properties/forms/shared/IconColorMenu.vue | 37 ++++--- .../viewers/shared/OutlinedInput.vue | 28 +++++ app/imports/constants/PROPERTIES.js | 15 ++- app/jsconfig.json | 3 +- 11 files changed, 192 insertions(+), 95 deletions(-) create mode 100644 app/imports/client/ui/properties/viewers/shared/OutlinedInput.vue diff --git a/app/imports/client/ui/components/ColorPicker.vue b/app/imports/client/ui/components/ColorPicker.vue index 92f304fe..ef73f90d 100644 --- a/app/imports/client/ui/components/ColorPicker.vue +++ b/app/imports/client/ui/components/ColorPicker.vue @@ -7,16 +7,18 @@ > diff --git a/app/imports/client/ui/properties/forms/AttributeForm.vue b/app/imports/client/ui/properties/forms/AttributeForm.vue index 77e66af1..ed9d01e2 100644 --- a/app/imports/client/ui/properties/forms/AttributeForm.vue +++ b/app/imports/client/ui/properties/forms/AttributeForm.vue @@ -259,6 +259,7 @@ /> + diff --git a/app/imports/client/ui/properties/forms/BranchForm.vue b/app/imports/client/ui/properties/forms/BranchForm.vue index 8893f57d..15e9f447 100644 --- a/app/imports/client/ui/properties/forms/BranchForm.vue +++ b/app/imports/client/ui/properties/forms/BranchForm.vue @@ -44,12 +44,14 @@ :error-messages="errors.silent" @change="change('silent', ...arguments)" /> - - - + + + + + + diff --git a/app/imports/client/ui/properties/forms/BuffForm.vue b/app/imports/client/ui/properties/forms/BuffForm.vue index 965f5810..71d0f538 100644 --- a/app/imports/client/ui/properties/forms/BuffForm.vue +++ b/app/imports/client/ui/properties/forms/BuffForm.vue @@ -93,6 +93,7 @@ @change="change('tags', ...arguments)" /> + diff --git a/app/imports/client/ui/properties/forms/FolderForm.vue b/app/imports/client/ui/properties/forms/FolderForm.vue index 9c993d3d..7b8f3a47 100644 --- a/app/imports/client/ui/properties/forms/FolderForm.vue +++ b/app/imports/client/ui/properties/forms/FolderForm.vue @@ -1,31 +1,8 @@ @@ -84,6 +84,13 @@ export default { components: { InlineEffect, }, + inject: { + theme: { + default: { + isDark: false, + }, + }, + }, props: { name: { type: String, From a58ccc0e0e500bb740c62037d9e96097da837b0c Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 20 Apr 2023 15:05:45 +0200 Subject: [PATCH 022/117] Outlined snackbars in red --- app/imports/client/ui/components/snackbars/SnackbarQueue.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/imports/client/ui/components/snackbars/SnackbarQueue.vue b/app/imports/client/ui/components/snackbars/SnackbarQueue.vue index 7f3eecf7..a17e085f 100644 --- a/app/imports/client/ui/components/snackbars/SnackbarQueue.vue +++ b/app/imports/client/ui/components/snackbars/SnackbarQueue.vue @@ -2,6 +2,8 @@ Date: Thu, 20 Apr 2023 15:37:12 +0200 Subject: [PATCH 023/117] Progress aligning and improving node/prop forms --- app/imports/api/library/LibraryNodes.js | 48 +- .../AddCreaturePropertyDialog.vue | 7 +- .../CreaturePropertiesTree.vue | 14 +- .../CreaturePropertyDialog.vue | 12 +- .../client/ui/library/LibraryAndNode.vue | 1 + .../client/ui/library/LibraryNodeDialog.vue | 485 +++++++++--------- .../client/ui/properties/PropertyForm.vue | 23 +- 7 files changed, 329 insertions(+), 261 deletions(-) diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index a81e7d71..1d5c136b 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -4,7 +4,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import SimpleSchema from 'simpl-schema'; import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js'; -import ChildSchema from '/imports/api/parenting/ChildSchema.js'; +import ChildSchema, { RefSchema } from '/imports/api/parenting/ChildSchema.js'; import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js'; import Libraries from '/imports/api/library/Libraries.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; @@ -15,6 +15,8 @@ import '/imports/api/library/methods/index.js'; import { updateReferenceNodeWork } from '/imports/api/library/methods/updateReferenceNode.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; import { restore } from '/imports/api/parenting/softRemove.js'; +import { getAncestry } from '/imports/api/parenting/parenting.js'; +import { reorderDocs } from '/imports/api/parenting/order.js'; let LibraryNodes = new Mongo.Collection('libraryNodes'); @@ -132,20 +134,56 @@ function assertNodeEditPermission(node, userId) { const insertNode = new ValidatedMethod({ name: 'libraryNodes.insert', - validate: null, + validate: new SimpleSchema({ + libraryNode: { + type: Object, + blackbox: true, + }, + parentRef: RefSchema, + }).validator(), mixins: [RateLimiterMixin], rateLimit: { numRequests: 5, timeInterval: 5000, }, - run(libraryNode) { + run({ libraryNode, parentRef }) { + // get the new ancestry + let { parentDoc, ancestors } = getAncestry({ parentRef }); + + // Check permission to edit + let root; + if (parentRef.collection === 'libraries') { + root = parentDoc; + } else if (parentRef.collection === 'libraryNodes') { + root = Libraries.findOne(parentDoc.ancestors[0].id); + } else { + throw `${parentRef.collection} is not a valid parent collection` + } + assertEditPermission(root, this.userId); + + // Set the ancestry of the library node + libraryNode.parent = parentRef; + libraryNode.ancestors = ancestors; + // Remove its ID if it came with one to force a random one to be generated + // server-side delete libraryNode._id; - assertNodeEditPermission(libraryNode, this.userId); - let nodeId = LibraryNodes.insert(libraryNode); + + // Insert the node + const nodeId = LibraryNodes.insert(libraryNode); + + // Update the node if it was a reference node if (libraryNode.type == 'reference') { libraryNode._id = nodeId; updateReferenceNodeWork(libraryNode, this.userId); } + + // Tree structure changed by insert, reorder the tree + reorderDocs({ + collection: LibraryNodes, + ancestorId: root._id, + }); + + // Return the id of the inserted node return nodeId; }, }); diff --git a/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue b/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue index a7a3c6af..a5d73807 100644 --- a/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue +++ b/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue @@ -68,8 +68,7 @@ - From 8289e9bd112b1869a87935a0f24e17849c798ecf Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 16:37:42 +0200 Subject: [PATCH 035/117] Form overhaul: Adjustment form --- .../client/ui/properties/PropertyForm.vue | 13 +++- .../ui/properties/forms/AdjustmentForm.vue | 76 ++++++++++--------- .../properties/forms/shared/FormSection.vue | 4 +- .../properties/forms/shared/FormSections.vue | 2 + 4 files changed, 53 insertions(+), 42 deletions(-) diff --git a/app/imports/client/ui/properties/PropertyForm.vue b/app/imports/client/ui/properties/PropertyForm.vue index a5293b08..40301e3f 100644 --- a/app/imports/client/ui/properties/PropertyForm.vue +++ b/app/imports/client/ui/properties/PropertyForm.vue @@ -7,6 +7,7 @@ style="gap: 8px" > - @@ -190,6 +191,7 @@ import CreaturePropertiesTree from '/imports/client/ui/creature/creatureProperti import OutlinedInput from '/imports/client/ui/properties/viewers/shared/OutlinedInput.vue'; import { getSuggestedChildren } from '/imports/constants/PROPERTIES.js'; import PROPERTIES from '/imports/constants/PROPERTIES.js'; +import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex.js'; const slotTypes = []; for (let key in PROPERTIES) { @@ -231,6 +233,11 @@ export default { if (!this.model?.type) return; return getSuggestedChildren(this.model.type); }, + schemaHasName() { + if (!this.model?.type) return true; + const schema = propertySchemasIndex[this.model.type]; + return schema.allowsKey('name'); + } }, mounted() { // Don't autofocus on mobile, it brings up the on-screen keyboard diff --git a/app/imports/client/ui/properties/forms/AdjustmentForm.vue b/app/imports/client/ui/properties/forms/AdjustmentForm.vue index 38a9a507..74d7a34c 100644 --- a/app/imports/client/ui/properties/forms/AdjustmentForm.vue +++ b/app/imports/client/ui/properties/forms/AdjustmentForm.vue @@ -7,7 +7,7 @@ > - - - + + + + + + + + + + + {{ name }} - + @@ -13,7 +13,7 @@ {{ name }} - + diff --git a/app/imports/client/ui/properties/forms/shared/FormSections.vue b/app/imports/client/ui/properties/forms/shared/FormSections.vue index 6010fb96..e9f06985 100644 --- a/app/imports/client/ui/properties/forms/shared/FormSections.vue +++ b/app/imports/client/ui/properties/forms/shared/FormSections.vue @@ -1,7 +1,9 @@ @@ -130,10 +56,12 @@ import propertyFormMixin from '/imports/client/ui/properties/forms/shared/proper import FormSection from '/imports/client/ui/properties/forms/shared/FormSection.vue'; import PROPERTIES from '/imports/constants/PROPERTIES.js'; import { SlotSchema } from '/imports/api/properties/Slots.js'; +import TagTargeting from '/imports/client/ui/properties/forms/shared/TagTargeting.vue'; export default { components: { FormSection, + TagTargeting, }, mixins: [propertyFormMixin], inject: { diff --git a/app/imports/client/ui/properties/forms/shared/TagTargeting.vue b/app/imports/client/ui/properties/forms/shared/TagTargeting.vue index 31b48b0b..fb9663a0 100644 --- a/app/imports/client/ui/properties/forms/shared/TagTargeting.vue +++ b/app/imports/client/ui/properties/forms/shared/TagTargeting.vue @@ -17,22 +17,22 @@
@@ -41,24 +41,24 @@ style="width: 90px; flex-grow: 0;" :items="['OR', 'NOT']" :value="extras.operation" - :error-messages="errors.extraTags && errors.extraTags[i]" - @change="change(['extraTags', i, 'operation'], ...arguments)" + :error-messages="errors[extraTagsField] && errors[extraTagsField][i]" + @change="change([extraTagsField, i, 'operation'], ...arguments)" /> mdi-delete @@ -80,6 +80,26 @@ export default { type: Object, required: true, }, + tagField: { + type: String, + default: 'targetTags', + }, + extraTagsField: { + type: String, + default: 'extraTags', + }, + tagHint: { + type: String, + default: 'Applied to properties that have all the listed tags', + }, + orHint: { + type: String, + default: 'Also applied to properties that have all of these tags', + }, + notHint: { + type: String, + default: 'Ignore properties that have any of these tags', + }, }, data() { return { @@ -90,18 +110,18 @@ export default { maxTags() { if (!this.model?.type) return 0; const schema = propertySchemasIndex[this.model.type]; - return schema.get('extraTags', 'maxCount'); + return schema.get(this.extraTagsField, 'maxCount'); }, extraTagsFull() { - if (!this.model.extraTags) return false; - return this.model.extraTags.length >= this.maxTags; + if (!this.model[this.extraTagsField]) return false; + return this.model[this.extraTagsField].length >= this.maxTags; }, }, methods: { addExtraTags() { this.addExtraTagsLoading = true; this.$emit('push', { - path: ['extraTags'], + path: [this.extraTagsField], value: { _id: Random.id(), operation: 'OR', From 0dbd5903b3a2cf5cd20fbe2a8f27be76914628e4 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:26:44 +0200 Subject: [PATCH 044/117] Form overhaul: Class Level Form --- app/imports/api/properties/ClassLevels.js | 6 -- .../ui/properties/forms/ClassLevelForm.vue | 87 +++++++------------ 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/app/imports/api/properties/ClassLevels.js b/app/imports/api/properties/ClassLevels.js index bbd76d58..5fd8a702 100644 --- a/app/imports/api/properties/ClassLevels.js +++ b/app/imports/api/properties/ClassLevels.js @@ -26,12 +26,6 @@ const ClassLevelSchema = createPropertySchema({ defaultValue: 1, max: STORAGE_LIMITS.levelMax, }, - // Filters out of UI if condition isn't met, but isn't otherwise enforced - slotFillerCondition: { - type: String, - optional: true, - max: STORAGE_LIMITS.calculation, - }, }); const ComputedOnlyClassLevelSchema = createPropertySchema({ diff --git a/app/imports/client/ui/properties/forms/ClassLevelForm.vue b/app/imports/client/ui/properties/forms/ClassLevelForm.vue index 15be3ac0..bb4d630f 100644 --- a/app/imports/client/ui/properties/forms/ClassLevelForm.vue +++ b/app/imports/client/ui/properties/forms/ClassLevelForm.vue @@ -1,41 +1,33 @@ @@ -70,9 +50,6 @@ import propertyFormMixin from '/imports/client/ui/properties/forms/shared/proper export default { mixins: [propertyFormMixin], - inject: { - context: { default: {} } - }, }; From 53958fde9286bd8deaa46d9e23eb109da13537d6 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:30:21 +0200 Subject: [PATCH 045/117] Form overhaul: Constant Form --- .../ui/properties/forms/ConstantForm.vue | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/app/imports/client/ui/properties/forms/ConstantForm.vue b/app/imports/client/ui/properties/forms/ConstantForm.vue index c6c5e07a..297ff737 100644 --- a/app/imports/client/ui/properties/forms/ConstantForm.vue +++ b/app/imports/client/ui/properties/forms/ConstantForm.vue @@ -1,36 +1,32 @@ From 44703a5aa5c86c74acb8249d5b6949dd07e0f01f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:37:32 +0200 Subject: [PATCH 046/117] Form overhaul: Container Form --- .../ui/properties/forms/ContainerForm.vue | 129 ++++++++---------- 1 file changed, 57 insertions(+), 72 deletions(-) diff --git a/app/imports/client/ui/properties/forms/ContainerForm.vue b/app/imports/client/ui/properties/forms/ContainerForm.vue index 5cef5978..6af54725 100644 --- a/app/imports/client/ui/properties/forms/ContainerForm.vue +++ b/app/imports/client/ui/properties/forms/ContainerForm.vue @@ -1,14 +1,45 @@ From ea2416aaea1384c30bb15dc76000c10e1b84e964 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:41:30 +0200 Subject: [PATCH 047/117] Form overhaul: Damage Form --- .../client/ui/properties/forms/BuffForm.vue | 1 - .../client/ui/properties/forms/DamageForm.vue | 49 +++++++------------ 2 files changed, 18 insertions(+), 32 deletions(-) diff --git a/app/imports/client/ui/properties/forms/BuffForm.vue b/app/imports/client/ui/properties/forms/BuffForm.vue index 871c8dda..e9273bbd 100644 --- a/app/imports/client/ui/properties/forms/BuffForm.vue +++ b/app/imports/client/ui/properties/forms/BuffForm.vue @@ -29,7 +29,6 @@ :error-messages="errors.target" @change="change('target', ...arguments)" /> - - - - - - + + + + + +
From 8a4bfa8475dc401e57612246cb8d119aecbc52ff Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:51:56 +0200 Subject: [PATCH 048/117] Form overhaul: Damage Multiplier Form --- .../ui/properties/forms/BuffRemoverForm.vue | 1 + .../properties/forms/DamageMultiplierForm.vue | 104 ++++++++---------- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/app/imports/client/ui/properties/forms/BuffRemoverForm.vue b/app/imports/client/ui/properties/forms/BuffRemoverForm.vue index 3059dce7..f0226238 100644 --- a/app/imports/client/ui/properties/forms/BuffRemoverForm.vue +++ b/app/imports/client/ui/properties/forms/BuffRemoverForm.vue @@ -70,6 +70,7 @@ @change="change('silent', ...arguments)" />
+
diff --git a/app/imports/client/ui/properties/forms/DamageMultiplierForm.vue b/app/imports/client/ui/properties/forms/DamageMultiplierForm.vue index 130fbc85..11e9053c 100644 --- a/app/imports/client/ui/properties/forms/DamageMultiplierForm.vue +++ b/app/imports/client/ui/properties/forms/DamageMultiplierForm.vue @@ -1,29 +1,23 @@ @@ -102,18 +96,6 @@ export default { data() { return { DAMAGE_TYPES, - values: [ - { - value: 0, - text: 'Immunity', - }, { - value: 0.5, - text: 'Resistance', - }, { - value: 2, - text: 'Vulnerability', - }, - ], damageTypeRules: [ value => { if (value && value.length) { From b65b4b4497a47c0f7afe8afada3b72cf3d1eff13 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:53:36 +0200 Subject: [PATCH 049/117] Form overhaul: Effect Form --- .../client/ui/properties/forms/EffectForm.vue | 293 ++++++------------ 1 file changed, 98 insertions(+), 195 deletions(-) diff --git a/app/imports/client/ui/properties/forms/EffectForm.vue b/app/imports/client/ui/properties/forms/EffectForm.vue index 1d386bf8..ef70df47 100644 --- a/app/imports/client/ui/properties/forms/EffectForm.vue +++ b/app/imports/client/ui/properties/forms/EffectForm.vue @@ -1,181 +1,103 @@ @@ -184,20 +106,18 @@ import getEffectIcon from '/imports/client/ui/utility/getEffectIcon.js'; import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js'; import attributeListMixin from '/imports/client/ui/properties/forms/shared/lists/attributeListMixin.js'; -import { EffectSchema } from '/imports/api/properties/Effects.js'; -import FormSection from '/imports/client/ui/properties/forms/shared/FormSection.vue'; +import TagTargeting from '/imports/client/ui/properties/forms/shared/TagTargeting.vue'; const ICON_SPIN_DURATION = 300; export default { components: { - FormSection, + TagTargeting, }, mixins: [propertyFormMixin, attributeListMixin], data() { return { displayedIcon: 'add', iconClass: '', - addExtraTagsLoading: false, oldOperation: undefined, operations: [ { value: 'base', text: 'Base Value' }, @@ -218,11 +138,6 @@ export default { radioGroup() { return this.model.targetByTags ? 'tags' : 'stats'; }, - extraTagsFull() { - if (!this.model.extraTags) return false; - let maxCount = EffectSchema.get('extraTags', 'maxCount'); - return this.model.extraTags.length >= maxCount; - }, needsValue() { switch (this.model.operation) { case 'base': return true; @@ -242,7 +157,7 @@ export default { operationHint() { switch (this.model.operation) { case 'base': return 'Stats take their largest base value, and then apply all other effects'; - case 'add': return 'Add this vaulue to the stat'; + case 'add': return 'Add this value to the stat'; case 'mul': return 'Multiply the stat by this value'; case 'min': return 'The stat will be at least this value'; case 'max': return 'The stat will not exceed this value'; @@ -279,32 +194,20 @@ export default { }, methods: { getEffectIcon, - changeTargetByTags(value) { + changeTargetByTags(value, ack) { if (value === 'stats') { - this.$emit('change', { path: ['targetByTags'], value: undefined }); + this.$emit('change', { path: ['targetByTags'], value: undefined, ack }); if (this.oldOperation && this.oldOperation !== this.model.operation) { this.$emit('change', { path: ['operation'], value: this.oldOperation }); } } else if (value === 'tags') { - this.$emit('change', { path: ['targetByTags'], value: true }); + this.$emit('change', { path: ['targetByTags'], value: true, ack }); if (this.model.operation !== 'add') { this.oldOperation = this.model.operation; this.$emit('change', { path: ['operation'], value: 'add' }); } } }, - addExtraTags() { - this.addExtraTagsLoading = true; - this.$emit('push', { - path: ['extraTags'], - value: { - _id: Random.id(), - operation: 'OR', - tags: [], - }, - ack: () => this.addExtraTagsLoading = false, - }); - }, } }; From c8a53a02356097e2b72cc278f2ef6765cd6dd959 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:54:38 +0200 Subject: [PATCH 050/117] Form overhaul: Feature Form --- .../ui/properties/forms/FeatureForm.vue | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/app/imports/client/ui/properties/forms/FeatureForm.vue b/app/imports/client/ui/properties/forms/FeatureForm.vue index 10aa74fd..9e814976 100644 --- a/app/imports/client/ui/properties/forms/FeatureForm.vue +++ b/app/imports/client/ui/properties/forms/FeatureForm.vue @@ -1,12 +1,5 @@ From f120ddb75a65d00abe88794071dad9fee82a0d66 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 20:56:20 +0200 Subject: [PATCH 051/117] Form ovehaul: Folder Form --- app/imports/client/ui/properties/forms/FolderForm.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/imports/client/ui/properties/forms/FolderForm.vue b/app/imports/client/ui/properties/forms/FolderForm.vue index 7b8f3a47..0d4e0825 100644 --- a/app/imports/client/ui/properties/forms/FolderForm.vue +++ b/app/imports/client/ui/properties/forms/FolderForm.vue @@ -2,7 +2,7 @@
- + Date: Tue, 16 May 2023 21:02:26 +0200 Subject: [PATCH 052/117] Form overhaul: Item Form --- .../client/ui/properties/forms/ItemForm.vue | 100 +++++++----------- 1 file changed, 37 insertions(+), 63 deletions(-) diff --git a/app/imports/client/ui/properties/forms/ItemForm.vue b/app/imports/client/ui/properties/forms/ItemForm.vue index 45ef9e5c..74899c8a 100644 --- a/app/imports/client/ui/properties/forms/ItemForm.vue +++ b/app/imports/client/ui/properties/forms/ItemForm.vue @@ -1,14 +1,6 @@ From ce07766fb40dbd28346387935e99e2e8b0d8df95 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 21:03:26 +0200 Subject: [PATCH 053/117] Form overhaul: Note Form --- .../client/ui/properties/forms/NoteForm.vue | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/app/imports/client/ui/properties/forms/NoteForm.vue b/app/imports/client/ui/properties/forms/NoteForm.vue index 36d693bb..f0e9456d 100644 --- a/app/imports/client/ui/properties/forms/NoteForm.vue +++ b/app/imports/client/ui/properties/forms/NoteForm.vue @@ -1,13 +1,5 @@ From 9f01b85df32c7956c572d5a746a001e8caf093a0 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 21:21:25 +0200 Subject: [PATCH 054/117] Form overhaul: Point Buy Form --- .../ui/properties/forms/PointBuyForm.vue | 44 +++++++++---------- .../ui/properties/forms/PointBuySpendForm.vue | 1 + 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/imports/client/ui/properties/forms/PointBuyForm.vue b/app/imports/client/ui/properties/forms/PointBuyForm.vue index 4698559d..8705e17a 100644 --- a/app/imports/client/ui/properties/forms/PointBuyForm.vue +++ b/app/imports/client/ui/properties/forms/PointBuyForm.vue @@ -1,26 +1,13 @@ @@ -188,6 +185,9 @@ export default { PointBuySpendForm, }, mixins: [propertyFormMixin, attributeListMixin], + inject: { + context: { default: {} } + }, data() { return { addRowLoading: false, diff --git a/app/imports/client/ui/properties/forms/PointBuySpendForm.vue b/app/imports/client/ui/properties/forms/PointBuySpendForm.vue index 1eeb8ed8..5fbbaf28 100644 --- a/app/imports/client/ui/properties/forms/PointBuySpendForm.vue +++ b/app/imports/client/ui/properties/forms/PointBuySpendForm.vue @@ -118,6 +118,7 @@ export default { const currentSpent = this.model.spent; let newSpent = currentSpent - row.spent; const costFunction = EJSON.clone(row.cost || this.model.cost); + if (!costFunction?.parseNode) return; if (costFunction) costFunction.parseLevel = 'reduce'; evaluateCalculation(costFunction, { value }); if (Number.isFinite(costFunction.value)) { From bf6fb358e6044c3b1fef8013d7d4155c3fa551e1 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 21:24:21 +0200 Subject: [PATCH 055/117] Form overhaul: Proficiency Form --- .../client/ui/properties/forms/EffectForm.vue | 2 +- .../ui/properties/forms/ProficiencyForm.vue | 80 +++++++++---------- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/app/imports/client/ui/properties/forms/EffectForm.vue b/app/imports/client/ui/properties/forms/EffectForm.vue index ef70df47..bfcafcb4 100644 --- a/app/imports/client/ui/properties/forms/EffectForm.vue +++ b/app/imports/client/ui/properties/forms/EffectForm.vue @@ -75,7 +75,7 @@ label="Stats" class="mr-2" multiple - chips + small-chips deletable-chips hint="Which stats will this effect apply to" persistent-hint diff --git a/app/imports/client/ui/properties/forms/ProficiencyForm.vue b/app/imports/client/ui/properties/forms/ProficiencyForm.vue index a8a18d09..b2246c10 100644 --- a/app/imports/client/ui/properties/forms/ProficiencyForm.vue +++ b/app/imports/client/ui/properties/forms/ProficiencyForm.vue @@ -1,49 +1,41 @@ From 4c34986fb735cdfec9a94749f21a1e189a10c216 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 16 May 2023 21:50:50 +0200 Subject: [PATCH 056/117] Form overhaul: Reference Form --- .../library/methods/updateReferenceNode.js | 22 ++--- .../AddCreaturePropertyDialog.vue | 3 +- .../client/ui/dialogStack/DialogStack.vue | 2 +- .../client/ui/library/LibraryNodeDialog.vue | 1 + .../ui/properties/forms/ReferenceForm.vue | 93 ++++++++++++------- 5 files changed, 77 insertions(+), 44 deletions(-) diff --git a/app/imports/api/library/methods/updateReferenceNode.js b/app/imports/api/library/methods/updateReferenceNode.js index f933d065..03d6d832 100644 --- a/app/imports/api/library/methods/updateReferenceNode.js +++ b/app/imports/api/library/methods/updateReferenceNode.js @@ -21,7 +21,7 @@ const updateReferenceNode = new ValidatedMethod({ numRequests: 5, timeInterval: 5000, }, - run({_id}) { + run({ _id }) { let userId = this.userId; let node = LibraryNodes.findOne(_id); assertDocEditPermission(node, userId); @@ -29,15 +29,15 @@ const updateReferenceNode = new ValidatedMethod({ }, }); -function writeCache(_id, cache){ - LibraryNodes.update(_id, {$set: {cache}}, { - selector: {type: 'reference'}, +function writeCache(_id, cache) { + LibraryNodes.update(_id, { $set: { cache } }, { + selector: { type: 'reference' }, }); } -function updateReferenceNodeWork(node, userId){ +function updateReferenceNodeWork(node, userId) { let cache = {} - if (!node.ref){ + if (!node.ref?.collection || !node.ref?.id) { writeCache(node._id, cache); return; } @@ -45,20 +45,20 @@ function updateReferenceNodeWork(node, userId){ try { doc = fetchDocByRef(node.ref); if (doc.removed) throw 'Property has been deleted'; - if (doc.ancestors[0].id !== node.ancestors[0].id){ + if (doc.ancestors[0].id !== node.ancestors[0].id) { library = fetchDocByRef(doc.ancestors[0]); assertViewPermission(library, userId) } - } catch(e){ - cache = {error: e.reason || e.message || e.toString()} + } catch (e) { + cache = { error: e.reason || e.message || e.toString() } writeCache(node._id, cache); return; } cache = { node: doc, }; - if (library){ - cache.library = {name: library.name}; + if (library) { + cache.library = { name: library.name }; } writeCache(node._id, cache); } diff --git a/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue b/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue index a2f03794..fa21fffd 100644 --- a/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue +++ b/app/imports/client/ui/creature/creatureProperties/AddCreaturePropertyDialog.vue @@ -59,7 +59,7 @@ > @@ -228,6 +228,7 @@ export default { default: undefined, }, hideLibraryTab: Boolean, + showLibraryOnlyProps: Boolean, }, reactiveProvide: { name: 'context', diff --git a/app/imports/client/ui/dialogStack/DialogStack.vue b/app/imports/client/ui/dialogStack/DialogStack.vue index bd08841e..d37b4476 100644 --- a/app/imports/client/ui/dialogStack/DialogStack.vue +++ b/app/imports/client/ui/dialogStack/DialogStack.vue @@ -88,7 +88,7 @@ // If the top dialog can't be closed with the backdrop, shake shake const topDialog = this.dialogs[this.dialogs.length - 1]; - if (topDialog.data.noBackdropClose) { + if (topDialog?.data?.noBackdropClose) { this.shakeTopDialog(); return; } diff --git a/app/imports/client/ui/library/LibraryNodeDialog.vue b/app/imports/client/ui/library/LibraryNodeDialog.vue index ee47e4fa..4b99d661 100644 --- a/app/imports/client/ui/library/LibraryNodeDialog.vue +++ b/app/imports/client/ui/library/LibraryNodeDialog.vue @@ -307,6 +307,7 @@ export default { hideLibraryTab: true, suggestedType, noBackdropClose: true, + showLibraryOnlyProps: true, }, callback(result){ if (!result) return; diff --git a/app/imports/client/ui/properties/forms/ReferenceForm.vue b/app/imports/client/ui/properties/forms/ReferenceForm.vue index 88032c03..e4afe1da 100644 --- a/app/imports/client/ui/properties/forms/ReferenceForm.vue +++ b/app/imports/client/ui/properties/forms/ReferenceForm.vue @@ -1,34 +1,65 @@ @@ -36,12 +67,12 @@ import TreeNodeView from '/imports/client/ui/properties/treeNodeViews/TreeNodeView.vue'; import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js'; import updateReferenceNode from '/imports/api/library/methods/updateReferenceNode.js'; - import PropertyField from '/imports/client/ui/properties/viewers/shared/PropertyField.vue'; + import OutlinedInput from '/imports/client/ui/properties/viewers/shared/OutlinedInput.vue'; export default { components: { TreeNodeView, - PropertyField, + OutlinedInput, }, mixins: [propertyFormMixin], data(){return { From 0a3ea7672fd2bdea140c570cc09bb275bf1e529b Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 17 May 2023 11:32:39 +0200 Subject: [PATCH 057/117] Form overhaul: roll -> trigger roll, savingThrow, skill, slot, slotfiller, spell, spellList, toggle, trigger --- app/imports/api/library/LibraryNodes.js | 2 +- .../ui/components/global/SmartToggle.vue | 6 + .../client/ui/properties/forms/BuffForm.vue | 13 - .../client/ui/properties/forms/RollForm.vue | 71 ++-- .../ui/properties/forms/SavingThrowForm.vue | 80 ++--- .../client/ui/properties/forms/SkillForm.vue | 141 ++++---- .../ui/properties/forms/SlotFillerForm.vue | 99 +----- .../client/ui/properties/forms/SlotForm.vue | 304 +++++++----------- .../client/ui/properties/forms/SpellForm.vue | 86 ++--- .../ui/properties/forms/SpellListForm.vue | 134 ++++---- .../client/ui/properties/forms/ToggleForm.vue | 128 +++----- .../ui/properties/forms/TriggerForm.vue | 193 ++++------- 12 files changed, 443 insertions(+), 814 deletions(-) diff --git a/app/imports/api/library/LibraryNodes.js b/app/imports/api/library/LibraryNodes.js index 1d5c136b..39264fb6 100644 --- a/app/imports/api/library/LibraryNodes.js +++ b/app/imports/api/library/LibraryNodes.js @@ -203,7 +203,7 @@ const updateLibraryNode = new ValidatedMethod({ }, mixins: [RateLimiterMixin], rateLimit: { - numRequests: 5, + numRequests: 15, timeInterval: 5000, }, run({ _id, path, value }) { diff --git a/app/imports/client/ui/components/global/SmartToggle.vue b/app/imports/client/ui/components/global/SmartToggle.vue index afbfff70..679dd83a 100644 --- a/app/imports/client/ui/components/global/SmartToggle.vue +++ b/app/imports/client/ui/components/global/SmartToggle.vue @@ -22,6 +22,12 @@ height="42" v-on="(value == option.value) ? {} : { click() { click(option.value) } }" > + + {{ option.icon }} + {{ option.name }} diff --git a/app/imports/client/ui/properties/forms/BuffForm.vue b/app/imports/client/ui/properties/forms/BuffForm.vue index e9273bbd..95d63e0a 100644 --- a/app/imports/client/ui/properties/forms/BuffForm.vue +++ b/app/imports/client/ui/properties/forms/BuffForm.vue @@ -83,19 +83,6 @@ import propertyFormMixin from '/imports/client/ui/properties/forms/shared/proper export default { mixins: [propertyFormMixin], - data() { - return { - targetOptions: [ - { - text: 'Self', - value: 'self', - }, { - text: 'Target', - value: 'target', - }, - ], - } - }, } diff --git a/app/imports/client/ui/properties/forms/RollForm.vue b/app/imports/client/ui/properties/forms/RollForm.vue index 87b4664a..55c7da3a 100644 --- a/app/imports/client/ui/properties/forms/RollForm.vue +++ b/app/imports/client/ui/properties/forms/RollForm.vue @@ -1,17 +1,6 @@ diff --git a/app/imports/client/ui/properties/forms/SavingThrowForm.vue b/app/imports/client/ui/properties/forms/SavingThrowForm.vue index b2bd4cf5..07c09ff0 100644 --- a/app/imports/client/ui/properties/forms/SavingThrowForm.vue +++ b/app/imports/client/ui/properties/forms/SavingThrowForm.vue @@ -1,25 +1,13 @@ @@ -87,25 +63,5 @@ import propertyFormMixin from '/imports/client/ui/properties/forms/shared/proper export default { mixins: [saveListMixin, propertyFormMixin], - computed: { - targetOptions() { - return [ - { - text: 'Self', - value: 'self', - }, { - text: 'Target', - value: 'target', - }, - ]; - }, - targetOptionHint() { - let hints = { - self: 'The save will be applied to the character taking the action', - target: 'The save will be applied to the targets of the action', - }; - return hints[this.model.target]; - } - }, }; diff --git a/app/imports/client/ui/properties/forms/SkillForm.vue b/app/imports/client/ui/properties/forms/SkillForm.vue index e7c2ab54..4126cfed 100644 --- a/app/imports/client/ui/properties/forms/SkillForm.vue +++ b/app/imports/client/ui/properties/forms/SkillForm.vue @@ -1,42 +1,49 @@ @@ -267,6 +279,15 @@ export default { this.dirty = true; this.updateAllowedLibraryCollections(); }, + showDependencyGraph() { + this.$store.commit('pushDialogStack', { + component: 'dependency-graph-dialog', + elementId: 'dependency-graph-button', + data: { + creatureId: this.model._id, + }, + }); + }, }, }; diff --git a/app/imports/client/ui/creature/character/CharacterSheetToolbar.vue b/app/imports/client/ui/creature/character/CharacterSheetToolbar.vue index c7ba4918..6878df30 100644 --- a/app/imports/client/ui/creature/character/CharacterSheetToolbar.vue +++ b/app/imports/client/ui/creature/character/CharacterSheetToolbar.vue @@ -55,13 +55,6 @@ Delete - - - - mdi-graph - Dependency Graph - - @@ -244,15 +237,6 @@ export default { } }); }, - showDependencyGraph() { - this.$store.commit('pushDialogStack', { - component: 'dependency-graph-dialog', - elementId: 'creature-menu', - data: { - creatureId: this.creatureId, - }, - }); - }, unshareWithMe() { updateUserSharePermissions.call({ docRef: { diff --git a/app/imports/client/ui/creature/dependencyGraph/DependencyGraphDialog.vue b/app/imports/client/ui/creature/dependencyGraph/DependencyGraphDialog.vue index b97bac27..3037960c 100644 --- a/app/imports/client/ui/creature/dependencyGraph/DependencyGraphDialog.vue +++ b/app/imports/client/ui/creature/dependencyGraph/DependencyGraphDialog.vue @@ -60,15 +60,16 @@ export default { // Convert ngraph to cytoscape // Convert Nodes const nodes = []; - console.log(this.computation); const loopNodes = getLoopNodes(this.computation); this.computation.dependencyGraph.forEachNode(function (node) { nodes.push({ data: { id: node.id, label: getNodeName(node) || node.id, - variable: !node.data, + variable: !node.data?.type, inLoop: loopNodes.includes(node.id), + propId: node.data?._id, + prop: node.data, }, }); }); @@ -80,9 +81,7 @@ export default { target: link.fromId, source: link.toId, linkType: link.data, - inLoop: loopNodes.includes(link.fromId) && loopNodes.includes(link.toId) - }, - scratch: { + inLoop: loopNodes.includes(link.fromId) && loopNodes.includes(link.toId), }, }) //{ data: { source: 'n0', target: 'n1' } } @@ -97,18 +96,23 @@ export default { name: 'klay', nodeDimensionsIncludeLabels: false, klay: { - // direction: 'RIGHT', + addUnnecessaryBendpoints: false, + aspectRatio: 1.6, + direction: 'RIGHT', edgeRouting: 'ORTHOGONAL', //'ORTHOGONAL', + edgeSpacingFactor: 0.5, feedbackEdges: true, - compactComponents: true, + inLayerSpacingFactor: 1.0, layoutHierarchy: true, - mergeEdges: true, - // mergeHierarchyCrossingEdges: true, + linearSegmentsDeflectionDampening: 0.3, + compactComponents: true, + mergeEdges: false, + mergeHierarchyCrossingEdges: false, + nodeLayering: 'NETWORK_SIMPLEX', nodePlacement: 'LINEAR_SEGMENTS', //default 'BRANDES_KOEPF' spacing: 20,//20, - edgeSpacingFactor: 2.0, //0.5, thoroughness: 12,//7, - separateConnectedComponents: false, + separateConnectedComponents: true, }, }, @@ -117,37 +121,56 @@ export default { selector: 'node', style: { 'content': 'data(label)', - //'text-opacity': 0.5, - 'text-valign': 'bottom', + //'text-opacity': 0.8, + 'text-valign': 'center', 'text-halign': 'center', - 'background-color': '#11479e', + 'background-color': '#303030', + 'text-background-color': '#303030', + 'color': '#fff', //'text-outline-color': '#888', //'text-outline-width': 3, 'text-background-opacity': 1, - 'text-background-color': '#888', 'text-background-shape': 'roundrectangle', - 'shape': 'round-rectangle' + 'text-background-padding': 2, + 'shape': 'round-rectangle', + 'width': 80, + 'height': 20, + 'compound-sizing-wrt-labels': 'include', + 'font-family': '"Roboto",sans-serif', } }, { selector: ele => ele.data().variable, + style: { + 'color': '#f44336', + 'font-family': 'monospace', + } + }, { + selector: ele => !!ele.data().propId, style: { 'background-color': '#B71C1C', + 'text-background-color': '#B71C1C', } }, { selector: 'edge', style: { 'width': 4, 'target-arrow-shape': 'triangle-backcurve', - 'line-color': '#9dbaea', - 'target-arrow-color': '#9dbaea', - 'curve-style': 'unbundled-bezier', + 'color': '#fff', + 'text-opacity': 0.8, + 'line-color': '#555', + 'target-arrow-color': '#555', + 'curve-style': 'unbundled-bezier',//'unbundled-bezier', 'label': 'data(linkType)', 'text-rotation': 'autorotate', + 'source-endpoint': 'outside-to-line-or-label', + 'target-endpoint': 'outside-to-line-or-label', } }, { selector: ele => ele.data().inLoop, style: { + 'color': '#fff', 'background-color': '#FF6D00', + 'text-background-color': '#FF6D00', 'line-color': '#FF6D00', 'target-arrow-color': '#FF6D00', } @@ -161,11 +184,11 @@ export default { }); this.cytoscape.on('tap', 'node', function(evt){ var node = evt.target; - console.log( 'tapped node', node.data() ); + console.log( node.data() ); }); this.cytoscape.on('tap', 'edge', function(evt){ var edge = evt.target; - console.log( 'tapped edge', edge ); + console.log( edge.data() ); }); }, meteor: { @@ -182,6 +205,7 @@ export default { .graph-container { width: 100%; height: 100%; + background-color: #151515; } .graph-container svg { From 376d3bc522d79699be6573b063178abb76594c6f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 1 Jun 2023 08:30:12 +0200 Subject: [PATCH 063/117] Added redis oplog with collection caching --- app/.meteor/packages | 3 +- app/.meteor/versions | 4 +- app/imports/server/config/redisCaching.js | 3 + .../server/publications/searchLibraryNodes.js | 96 +++++++++++-------- app/packages/redis-oplog | 1 + app/redis-settings.json | 19 ++++ app/server/main.js | 1 + 7 files changed, 81 insertions(+), 46 deletions(-) create mode 100644 app/imports/server/config/redisCaching.js create mode 160000 app/packages/redis-oplog create mode 100644 app/redis-settings.json diff --git a/app/.meteor/packages b/app/.meteor/packages index 8e6c12c3..06788bcc 100644 --- a/app/.meteor/packages +++ b/app/.meteor/packages @@ -3,6 +3,7 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. +zegenie:redis-oplog accounts-password@2.3.4 random@1.2.1 underscore@1.0.13 @@ -46,7 +47,7 @@ ostrio:files simple:rest-bearer-token-parser simple:rest-json-error-handler littledata:synced-cron -mdg:meteor-apm-agent +#mdg:meteor-apm-agent typescript@4.9.4 seba:minifiers-autoprefixer mixmax:smart-disconnect diff --git a/app/.meteor/versions b/app/.meteor/versions index 03d4f323..578fce81 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -52,10 +52,8 @@ inter-process-messaging@0.1.1 lai:collection-extensions@0.3.0 launch-screen@1.3.0 littledata:synced-cron@1.5.1 -livedata@1.0.18 localstorage@1.2.0 logging@1.3.2 -mdg:meteor-apm-agent@3.5.1 mdg:validated-method@1.3.0 meteor@1.11.2 meteor-base@1.5.1 @@ -76,7 +74,6 @@ mongo@1.16.6 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 -mongo-livedata@1.0.12 npm-mongo@4.16.0 oauth@2.2.0 oauth2@1.3.2 @@ -127,4 +124,5 @@ underscore@1.0.13 url@1.3.2 webapp@1.13.5 webapp-hashing@1.1.1 +zegenie:redis-oplog@2.0.16 zer0th:meteor-vuetify-loader@0.1.41 diff --git a/app/imports/server/config/redisCaching.js b/app/imports/server/config/redisCaching.js new file mode 100644 index 00000000..45132ac3 --- /dev/null +++ b/app/imports/server/config/redisCaching.js @@ -0,0 +1,3 @@ +import LibraryNodes from '/imports/api/library/LibraryNodes.js'; + +LibraryNodes.startCaching(); diff --git a/app/imports/server/publications/searchLibraryNodes.js b/app/imports/server/publications/searchLibraryNodes.js index cedbc7a8..bbfcea84 100644 --- a/app/imports/server/publications/searchLibraryNodes.js +++ b/app/imports/server/publications/searchLibraryNodes.js @@ -5,27 +5,33 @@ import getCreatureLibraryIds from '/imports/api/library/getCreatureLibraryIds.js import getUserLibraryIds from '/imports/api/library/getUserLibraryIds.js'; import { assertViewPermission } from '/imports/api/sharing/sharingPermissions.js'; -Meteor.publish('selectedLibraryNodes', function(selectedNodeIds){ +function escapeRegex(string) { + return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, ''); +} + +Meteor.publish('selectedLibraryNodes', function (selectedNodeIds) { check(selectedNodeIds, Array); // Limit to 20 selected nodes - if (selectedNodeIds.length > 20){ + if (selectedNodeIds.length > 20) { selectedNodeIds = selectedNodeIds.slice(0, 20); } let libraryViewPermissions = {}; // Check view permissions of all libraries - for (let id of selectedNodeIds){ + for (let id of selectedNodeIds) { let node = LibraryNodes.findOne(id); if (!node) continue; let libraryId = node.ancestors[0].id; - if (libraryViewPermissions[id]){ + if (libraryViewPermissions[id]) { continue; } else { - let library = Libraries.findOne(libraryId, {fields: { - owner: 1, - readers: 1, - writers: 1, - public: 1, - }}); + let library = Libraries.findOne(libraryId, { + fields: { + owner: 1, + readers: 1, + writers: 1, + public: 1, + } + }); assertViewPermission(library, this.userId); libraryViewPermissions[id] = true; } @@ -33,15 +39,15 @@ Meteor.publish('selectedLibraryNodes', function(selectedNodeIds){ // Return all nodes and their children return [LibraryNodes.find({ $or: [ - {_id: {$in: selectedNodeIds}}, - {'ancestors.id': {$in: selectedNodeIds}}, + { _id: { $in: selectedNodeIds } }, + { 'ancestors.id': { $in: selectedNodeIds } }, ], })]; }); -Meteor.publish('searchLibraryNodes', function(creatureId){ +Meteor.publish('searchLibraryNodes', function (creatureId) { let self = this; - this.autorun(function (){ + this.autorun(function () { let type = self.data('type'); if (!type) return []; @@ -60,20 +66,20 @@ Meteor.publish('searchLibraryNodes', function(creatureId){ // Build a filter for nodes in those libraries that match the type let filter = { - 'ancestors.id': {$in: libraryIds}, - removed: {$ne: true}, - tags: {$ne: []}, // Only tagged library nodes are considered + 'ancestors.id': { $in: libraryIds }, + removed: { $ne: true }, + tags: { $ne: [] }, // Only tagged library nodes are considered }; - if (type){ + if (type) { filter.$or = [{ - type, - },{ - type: 'slotFiller', - slotFillerType: type, + type, + }, { + type: 'slotFiller', + slotFillerType: type, }]; } - this.autorun(function(){ + this.autorun(function () { // Get the limit of the documents the user can fetch var limit = self.data('limit') || 32; check(limit, Number); @@ -83,28 +89,34 @@ Meteor.publish('searchLibraryNodes', function(creatureId){ check(searchTerm, String); let options = undefined; - if (searchTerm){ - filter.$text = {$search: searchTerm}; + if (searchTerm) { + filter.name = { $regex: escapeRegex(searchTerm), '$options': 'i' }; + // filter.$text = {$search: searchTerm}; options = { + /* // relevant documents have a higher score. fields: { score: { $meta: 'textScore' } }, + */ sort: { // `score` property specified in the projection fields above. - score: { $meta: 'textScore' }, + // score: { $meta: 'textScore' }, 'ancestors.0.id': 1, name: 1, order: 1, } } } else { - delete filter.$text - options = {sort: { - 'ancestors.0.id': 1, - name: 1, - order: 1, - }}; + //delete filter.$text + delete filter.name; + options = { + sort: { + 'ancestors.0.id': 1, + name: 1, + order: 1, + } + }; } options.limit = limit; @@ -118,17 +130,17 @@ Meteor.publish('searchLibraryNodes', function(creatureId){ Mongo.Collection._publishCursor(libraries, self, 'libraries'); let observeHandle = cursor.observeChanges({ - added: function (id, fields) { - fields._searchResult = true; - self.added('libraryNodes', id, fields); - }, - changed: function (id, fields) { - self.changed('libraryNodes', id, fields); - }, - removed: function (id) { - self.removed('libraryNodes', id); - } + added: function (id, fields) { + fields._searchResult = true; + self.added('libraryNodes', id, fields); }, + changed: function (id, fields) { + self.changed('libraryNodes', id, fields); + }, + removed: function (id) { + self.removed('libraryNodes', id); + } + }, // Publications don't mutate the documents { nonMutatingCallbacks: true } ); diff --git a/app/packages/redis-oplog b/app/packages/redis-oplog new file mode 160000 index 00000000..83e302c1 --- /dev/null +++ b/app/packages/redis-oplog @@ -0,0 +1 @@ +Subproject commit 83e302c15456d6744047c50fc8d1add9e739b001 diff --git a/app/redis-settings.json b/app/redis-settings.json new file mode 100644 index 00000000..45fc18d7 --- /dev/null +++ b/app/redis-settings.json @@ -0,0 +1,19 @@ +{ + "redisOplog": { + "redis": { + "port": 6379, + "host": "127.0.0.1" + }, + "retryIntervalMs": 1000, + "mutationDefaults": { + "optimistic": true, + "pushToRedis": true + }, + "cacheTimeout": 1800000, + "cacheTimer": 300000, + "secondaryReads": null, + "raceDetectionDelay": 1000, + "raceDetection": true, + "debug": false + } +} \ No newline at end of file diff --git a/app/server/main.js b/app/server/main.js index 642113e0..eddb6d67 100644 --- a/app/server/main.js +++ b/app/server/main.js @@ -5,6 +5,7 @@ import '/imports/server/rest/index.js'; import '/imports/server/config/accountsEmailConfig.js'; import '/imports/server/config/simpleSchemaDebug.js'; import '/imports/server/config/SyncedCronConfig.js'; +import '/imports/server/config/redisCaching.js'; import '/imports/server/publications/index.js'; import '/imports/server/cron/deleteSoftRemovedDocuments.js'; import '/imports/api/parenting/organizeMethods.js'; From 2b4ab6258d38764f5f57c03af628cd13dd7ca680 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 1 Jun 2023 11:01:39 +0200 Subject: [PATCH 064/117] Added redis-oplog as submodule --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..509c092d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "app/packages/redis-oplog"] + path = app/packages/redis-oplog + url = https://github.com/ramezrafla/redis-oplog.git From 513c0f7148f83084c7189c226e54b4e4bb2cd124 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 1 Jun 2023 11:19:17 +0200 Subject: [PATCH 065/117] Added health check api endpoint --- app/imports/server/config/redisCaching.js | 2 +- .../rest/apiPublications/healthCheck.js | 37 +++++++++++++++++++ .../server/rest/apiPublications/index.js | 1 + app/package-lock.json | 2 +- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 app/imports/server/rest/apiPublications/healthCheck.js diff --git a/app/imports/server/config/redisCaching.js b/app/imports/server/config/redisCaching.js index 45132ac3..7ed21c26 100644 --- a/app/imports/server/config/redisCaching.js +++ b/app/imports/server/config/redisCaching.js @@ -1,3 +1,3 @@ import LibraryNodes from '/imports/api/library/LibraryNodes.js'; -LibraryNodes.startCaching(); +LibraryNodes.startCaching?.(); diff --git a/app/imports/server/rest/apiPublications/healthCheck.js b/app/imports/server/rest/apiPublications/healthCheck.js new file mode 100644 index 00000000..c6aa00a8 --- /dev/null +++ b/app/imports/server/rest/apiPublications/healthCheck.js @@ -0,0 +1,37 @@ +// A simple endpoint that does a single round trip to the database to check everything is working + +const HealthCheckCollection = new Mongo.Collection('healthCheck'); + +// Don't use redis oplog optimization on this collection, we want to hit the database every time +HealthCheckCollection.disableRedis?.(); + +const healthCheckDoc = { + status: 'ok', +}; + +// Add the health check doc on startup if it's missing +// There should only be this single doc in the collection +// A capped collection would be marginally faster, but it's a pain to make one in Meteor +Meteor.startup(function () { + if (!HealthCheckCollection.findOne()) { + HealthCheckCollection.insert(healthCheckDoc); + } +}); + +Meteor.method('api-status', function () { + let dbHealthDoc; + try { + dbHealthDoc = HealthCheckCollection.findOne(); + } catch (e) { + this.setHttpStatusCode(503); + } + if (dbHealthDoc?.status === 'ok') { + this.setHttpStatusCode(200); + } else { + this.setHttpStatusCode(500); + } + return dbHealthDoc || {}; +}, { + httpMethod: 'GET', + url: 'api/status' +}); diff --git a/app/imports/server/rest/apiPublications/index.js b/app/imports/server/rest/apiPublications/index.js index 765d7988..e95d23db 100644 --- a/app/imports/server/rest/apiPublications/index.js +++ b/app/imports/server/rest/apiPublications/index.js @@ -1 +1,2 @@ import './creature.js'; +import './healthCheck.js'; diff --git a/app/package-lock.json b/app/package-lock.json index dee36e3a..9fe1b74e 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1917,7 +1917,7 @@ "lodash.omit": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=" + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" }, "lodash.template": { "version": "4.5.0", From 1096c53f49625731763ff4f5e11bf5b9e8febb64 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 5 Jun 2023 15:44:53 +0200 Subject: [PATCH 066/117] Improved slot filling UI usability --- .../methods/getSlotFillFilter.js | 6 +- .../methods/getSlotFillFilter.test.js | 1 - .../computeSlotQuantityFilled.js | 5 +- .../computeByType/computeSlot.js | 4 +- .../api/library/getCreatureLibraryIds.js | 5 +- app/imports/api/utility/escapeRegex.js | 3 + .../ui/creature/slots/SlotFillDialog.vue | 198 +++++++++++------- .../library/LibraryNodeExpansionContent.vue | 38 +++- app/imports/server/publications/library.js | 7 +- .../server/publications/searchLibraryNodes.js | 5 +- .../server/publications/slotFillers.js | 35 ++-- 11 files changed, 182 insertions(+), 125 deletions(-) create mode 100644 app/imports/api/utility/escapeRegex.js diff --git a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js index a25e7bf4..f6a5e7f9 100644 --- a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js +++ b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js @@ -1,7 +1,7 @@ export default function getSlotFillFilter({ slot, libraryIds }) { - if (!slot) throw 'Slot is required'; - if (!libraryIds) throw 'LibraryIds is required'; + if (!slot) throw 'Slot is required for getSlotFillFilter'; + if (!libraryIds) throw 'LibraryIds is required for getSlotFillFilter'; let filter = { removed: { $ne: true }, @@ -13,7 +13,6 @@ export default function getSlotFillFilter({ slot, libraryIds }) { $or: [{ type: slot.slotType }, { - type: 'slotFiller', slotFillerType: slot.slotType, }] }); @@ -22,7 +21,6 @@ export default function getSlotFillFilter({ slot, libraryIds }) { $or: [{ type: 'classLevel', }, { - type: 'slotFiller', slotFillerType: 'classLevel', }] }); diff --git a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.test.js b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.test.js index 2e4048d2..5ca0f4ed 100644 --- a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.test.js +++ b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.test.js @@ -50,7 +50,6 @@ describe('Slot fill filter', function () { $or: [{ type: 'feature' }, { - type: 'slotFiller', slotFillerType: 'feature', }], }]); diff --git a/app/imports/api/engine/computation/buildComputation/computeSlotQuantityFilled.js b/app/imports/api/engine/computation/buildComputation/computeSlotQuantityFilled.js index e3adabc8..e9e4a287 100644 --- a/app/imports/api/engine/computation/buildComputation/computeSlotQuantityFilled.js +++ b/app/imports/api/engine/computation/buildComputation/computeSlotQuantityFilled.js @@ -2,7 +2,7 @@ * Only computes `totalFilled`, need to compute `quantityExpected.value` * before `spacesLeft` can be computed */ -export default function computeSlotQuantityFilled(node, dependencyGraph){ +export default function computeSlotQuantityFilled(node, dependencyGraph) { let slot = node.node; if (slot.type !== 'propertySlot') return; slot.totalFilled = 0; @@ -10,9 +10,8 @@ export default function computeSlotQuantityFilled(node, dependencyGraph){ let childProp = child.node; dependencyGraph.addLink(slot._id, childProp._id, 'slotFill'); if ( - childProp.type === 'slotFiller' && Number.isFinite(childProp.slotQuantityFilled) - ){ + ) { slot.totalFilled += childProp.slotQuantityFilled; } else { slot.totalFilled++; diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeSlot.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeSlot.js index 9e9dc07b..8128719a 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeSlot.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeSlot.js @@ -1,6 +1,6 @@ -export default function computSlot(computation, node){ +export default function computeSlot(computation, node) { const prop = node.data; - if (prop.quantityExpected && prop.quantityExpected.value){ + if (prop.quantityExpected && prop.quantityExpected.value) { prop.spaceLeft = prop.quantityExpected.value - prop.totalFilled; } } diff --git a/app/imports/api/library/getCreatureLibraryIds.js b/app/imports/api/library/getCreatureLibraryIds.js index beb9c7d3..a951a042 100644 --- a/app/imports/api/library/getCreatureLibraryIds.js +++ b/app/imports/api/library/getCreatureLibraryIds.js @@ -4,6 +4,7 @@ import getUserLibraryIds from './getUserLibraryIds'; import { intersection, union } from 'lodash'; export default function getCreatureLibraryIds(creature, userId) { + if (!userId) console.log('no userId, returning empty array'); if (!userId) return []; // Get the ids of libraries the user is permitted to view @@ -17,14 +18,14 @@ export default function getCreatureLibraryIds(creature, userId) { allowedLibraryCollections: 1, } }); - if (!creature) return []; + if (!creature) return userLibIds; } // If the creature does not restrict the libraries, let it use them all if (!creature.allowedLibraryCollections && !creature.allowedLibraries) { return userLibIds; } - + // Get the ids of the libraries that the creature allows const allowedCollections = creature.allowedLibraryCollections || []; let creatureLibIds = creature.allowedLibraries || []; diff --git a/app/imports/api/utility/escapeRegex.js b/app/imports/api/utility/escapeRegex.js new file mode 100644 index 00000000..8ec4073c --- /dev/null +++ b/app/imports/api/utility/escapeRegex.js @@ -0,0 +1,3 @@ +export default function escapeRegex(string) { + return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, ''); +} \ No newline at end of file diff --git a/app/imports/client/ui/creature/slots/SlotFillDialog.vue b/app/imports/client/ui/creature/slots/SlotFillDialog.vue index 8b2d24f0..15fc925b 100644 --- a/app/imports/client/ui/creature/slots/SlotFillDialog.vue +++ b/app/imports/client/ui/creature/slots/SlotFillDialog.vue @@ -26,7 +26,7 @@ :string="model.description" />

- {{ slotPropertyTypeName }} with tags: + {{ slotPropertyTypeName }} with library tags:

- - - - diff --git a/app/imports/client/ui/creature/creatureProperties/CreaturePropertyInsertForm.vue b/app/imports/client/ui/creature/creatureProperties/CreaturePropertyInsertForm.vue deleted file mode 100644 index e2deb0bd..00000000 --- a/app/imports/client/ui/creature/creatureProperties/CreaturePropertyInsertForm.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - diff --git a/app/imports/client/ui/dialogStack/DialogComponentIndex.js b/app/imports/client/ui/dialogStack/DialogComponentIndex.js index 59ce7ec6..5ba5ce8e 100644 --- a/app/imports/client/ui/dialogStack/DialogComponentIndex.js +++ b/app/imports/client/ui/dialogStack/DialogComponentIndex.js @@ -3,7 +3,6 @@ import InsertPropertyDialog from '/imports/client/ui/properties/InsertPropertyDi import CharacterCreationDialog from '/imports/client/ui/creature/character/CharacterCreationDialog.vue'; import CastSpellWithSlotDialog from '/imports/client/ui/properties/components/spells/CastSpellWithSlotDialog.vue'; import CreatureFormDialog from '/imports/client/ui/creature/CreatureFormDialog.vue'; -import CreaturePropertyCreationDialog from '/imports/client/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue'; import CreaturePropertyDialog from '/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue'; import CreaturePropertyFromLibraryDialog from '/imports/client/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'; import CreatureRootDialog from '/imports/client/ui/creature/character/CreatureRootDialog.vue'; @@ -26,7 +25,6 @@ const LibraryCollectionCreationDialog = () => import('/imports/client/ui/library const LibraryCollectionEditDialog = () => import('/imports/client/ui/library/LibraryCollectionEditDialog.vue'); const LibraryCreationDialog = () => import('/imports/client/ui/library/LibraryCreationDialog.vue'); const LibraryEditDialog = () => import('/imports/client/ui/library/LibraryEditDialog.vue'); -const LibraryNodeCreationDialog = () => import('/imports/client/ui/library/LibraryNodeCreationDialog.vue'); const LibraryNodeDialog = () => import('/imports/client/ui/library/LibraryNodeDialog.vue'); const MoveLibraryNodeDialog = () => import('/imports/client/ui/library/MoveLibraryNodeDialog.vue'); const SelectCreaturesDialog = () => import('/imports/client/ui/tabletop/SelectCreaturesDialog.vue'); @@ -39,7 +37,6 @@ export default { CastSpellWithSlotDialog, CharacterCreationDialog, CreatureFormDialog, - CreaturePropertyCreationDialog, CreaturePropertyDialog, CreaturePropertyFromLibraryDialog, CreatureRootDialog, @@ -55,7 +52,6 @@ export default { LibraryCollectionEditDialog, LibraryCreationDialog, LibraryEditDialog, - LibraryNodeCreationDialog, LibraryNodeDialog, MoveLibraryNodeDialog, SelectCreaturesDialog, diff --git a/app/imports/client/ui/library/LibraryNodeCreationDialog.vue b/app/imports/client/ui/library/LibraryNodeCreationDialog.vue deleted file mode 100644 index b4748be7..00000000 --- a/app/imports/client/ui/library/LibraryNodeCreationDialog.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - - - diff --git a/app/imports/client/ui/library/LibraryNodeInsertForm.vue b/app/imports/client/ui/library/LibraryNodeInsertForm.vue deleted file mode 100644 index 3263558e..00000000 --- a/app/imports/client/ui/library/LibraryNodeInsertForm.vue +++ /dev/null @@ -1,120 +0,0 @@ - - - - - From 35ebed81ddfef5aac2413f03d1b86c9cb78b8cae Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 11:16:07 +0200 Subject: [PATCH 072/117] Fixed slot fill test in library prop edit --- .../client/ui/creature/slots/SlotFillDialog.vue | 3 ++- .../client/ui/properties/forms/SlotForm.vue | 17 ++++++++++++----- app/imports/server/publications/slotFillers.js | 15 ++++++++++----- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/imports/client/ui/creature/slots/SlotFillDialog.vue b/app/imports/client/ui/creature/slots/SlotFillDialog.vue index c2f5042f..7eeddf22 100644 --- a/app/imports/client/ui/creature/slots/SlotFillDialog.vue +++ b/app/imports/client/ui/creature/slots/SlotFillDialog.vue @@ -153,6 +153,7 @@ class="ma-4" > - + Test Slot - + Date: Wed, 7 Jun 2023 11:37:44 +0200 Subject: [PATCH 073/117] Fixed overriden props showing up in stats tab --- .../client/ui/creature/character/characterSheetTabs/StatsTab.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue index 73f46cb2..a5fd932a 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -530,6 +530,7 @@ export default { { inactive: { $ne: true } }, { type: 'toggle' }, ], + overridden: {$ne: true}, removed: { $ne: true }, type: { $in: [ From d4e5a2a52944a66fdaf6cc33707642f8b748a058 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 11:55:31 +0200 Subject: [PATCH 074/117] Fixed visual glitch with filling 1 space slot with 0 cost filler --- .../ui/creature/buildTree/BuildTreeNode.vue | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/imports/client/ui/creature/buildTree/BuildTreeNode.vue b/app/imports/client/ui/creature/buildTree/BuildTreeNode.vue index 26629374..0e037ae9 100644 --- a/app/imports/client/ui/creature/buildTree/BuildTreeNode.vue +++ b/app/imports/client/ui/creature/buildTree/BuildTreeNode.vue @@ -43,7 +43,7 @@ {{ node.name }}
@@ -175,7 +175,8 @@ export default { this.children.length === 1 && this.children[0].node.type !== 'propertySlot' && this.node.quantityExpected && - this.node.quantityExpected.value === 1; + this.node.quantityExpected.value === 1 && + !this.canFill; }, isSlot(){ return this.node.type === 'propertySlot'; @@ -185,15 +186,18 @@ export default { }, canFillWithOne(){ return this.isSlot && - this.node.quantityExpected && + this.canFill && + this.node.quantityExpected && this.node.quantityExpected.value === 1 && - this.node.spaceLeft === 1 + this.node.spaceLeft === 1 && + !this.children?.length; }, canFillWithMany(){ - return this.isSlot && ( + return this.isSlot && this.canFill && ( !this.node.quantityExpected || this.node.quantityExpected.value === 0 || - (this.node.quantityExpected.value > 1 && this.node.spaceLeft > 0) + (this.node.quantityExpected.value > 1 && this.node.spaceLeft > 0) || + (this.node.quantityExpected.value === 1 && this.children?.length) ); }, hasChildren(){ From 15ff16bb8c37163041098ac9a6cec7f84a20b25a Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 11:57:28 +0200 Subject: [PATCH 075/117] Fixed multi-click on restore archive character --- app/imports/client/ui/files/ArchiveFileCard.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/app/imports/client/ui/files/ArchiveFileCard.vue b/app/imports/client/ui/files/ArchiveFileCard.vue index f1de6d3b..8d4ddcd1 100644 --- a/app/imports/client/ui/files/ArchiveFileCard.vue +++ b/app/imports/client/ui/files/ArchiveFileCard.vue @@ -10,6 +10,7 @@ Restore From 4d6c6b60946c5104518d2acd4a09c943965126a4 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 12:29:29 +0200 Subject: [PATCH 076/117] Added advantage to attributes ability checks will be automatically made with adv when appropriate --- .../computeVariableAsAttribute.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js index 6ec5df2d..2e518127 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js @@ -1,6 +1,6 @@ import getAggregatorResult from './getAggregatorResult.js'; -export default function computeVariableAsAttribute(computation, node, prop){ +export default function computeVariableAsAttribute(computation, node, prop) { let result = getAggregatorResult(node) || 0; prop.total = result; @@ -9,8 +9,20 @@ export default function computeVariableAsAttribute(computation, node, prop){ // Proficiency prop.proficiency = node.data.proficiency; + // Advantage/disadvantage + const aggregator = node.data.effectAggregator; + if (aggregator) { + if (aggregator.advantage && !aggregator.disadvantage) { + prop.advantage = 1; + } else if (aggregator.disadvantage && !aggregator.advantage) { + prop.advantage = -1; + } else { + prop.advantage = 0; + } + } + // Ability scores get modifiers - if (prop.attributeType === 'ability'){ + if (prop.attributeType === 'ability') { prop.modifier = Math.floor((prop.value - 10) / 2); } From 6bc737f8500cebad408f402aeb60f9bc82ad3a2e Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 12:29:54 +0200 Subject: [PATCH 077/117] Fixed advantage with new action scope prefix: '~' --- app/imports/api/properties/Attributes.js | 7 +++++++ app/imports/client/ui/components/RollPopup.vue | 5 ++++- .../properties/components/actions/ActionCard.vue | 2 +- .../properties/components/actions/EventButton.vue | 2 +- .../components/attributes/AbilityListTile.vue | 14 +++++++++++++- .../components/attributes/AttributeCardContent.vue | 14 +++++++++++++- .../components/attributes/SpellSlotCard.vue | 2 +- .../properties/components/skills/SkillListTile.vue | 2 +- .../client/ui/properties/viewers/ActionViewer.vue | 2 +- 9 files changed, 42 insertions(+), 8 deletions(-) diff --git a/app/imports/api/properties/Attributes.js b/app/imports/api/properties/Attributes.js index 94e5d657..8835661a 100644 --- a/app/imports/api/properties/Attributes.js +++ b/app/imports/api/properties/Attributes.js @@ -173,6 +173,13 @@ let ComputedOnlyAttributeSchema = createPropertySchema({ optional: true, removeBeforeCompute: true, }, + // Attributes with advantage grant it to all skills based on the attribute + advantage: { + type: SimpleSchema.Integer, + optional: true, + allowedValues: [-1, 0, 1], + removeBeforeCompute: true, + }, // The computed creature constitution modifier for hit dice constitutionMod: { type: Number, diff --git a/app/imports/client/ui/components/RollPopup.vue b/app/imports/client/ui/components/RollPopup.vue index f9f20f66..d7cb25b8 100644 --- a/app/imports/client/ui/components/RollPopup.vue +++ b/app/imports/client/ui/components/RollPopup.vue @@ -19,7 +19,10 @@ - + Disadvantage diff --git a/app/imports/client/ui/properties/components/actions/ActionCard.vue b/app/imports/client/ui/properties/components/actions/ActionCard.vue index b2c67042..55553f48 100644 --- a/app/imports/client/ui/properties/components/actions/ActionCard.vue +++ b/app/imports/client/ui/properties/components/actions/ActionCard.vue @@ -211,7 +211,7 @@ export default { doAction.call({ actionId: this.model._id, scope: { - $attackAdvantage: advantage, + '~attackAdvantage': { value: advantage }, } }, error => { this.doActionLoading = false; diff --git a/app/imports/client/ui/properties/components/actions/EventButton.vue b/app/imports/client/ui/properties/components/actions/EventButton.vue index 27773e04..f7333739 100644 --- a/app/imports/client/ui/properties/components/actions/EventButton.vue +++ b/app/imports/client/ui/properties/components/actions/EventButton.vue @@ -53,7 +53,7 @@ export default { doAction.call({ actionId: this.model._id, scope: { - $attackAdvantage: advantage, + '~attackAdvantage': { value: advantage }, } }, error => { this.doActionLoading = false; diff --git a/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue b/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue index 2cc2eb95..3a1d6866 100644 --- a/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue +++ b/app/imports/client/ui/properties/components/attributes/AbilityListTile.vue @@ -46,6 +46,18 @@ {{ model.name }} + + mdi-chevron-double-up + + + mdi-chevron-double-down + @@ -89,7 +101,7 @@ export default { doCheck.call({ propId: this.model._id, scope: { - $checkAdvantage: advantage, + '~checkAdvantage': { value: advantage }, }, }, error => { this.checkLoading = false; diff --git a/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue b/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue index c1976688..60c59d90 100644 --- a/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue +++ b/app/imports/client/ui/properties/components/attributes/AttributeCardContent.vue @@ -30,6 +30,18 @@ {{ model.name }} + + mdi-chevron-double-up + + + mdi-chevron-double-down +
@@ -75,7 +87,7 @@ export default { doCheck.call({ propId: this.model._id, scope: { - $checkAdvantage: advantage, + '~checkAdvantage': { value: advantage }, }, }, error => { this.checkLoading = false; diff --git a/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue b/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue index 3abae994..66980849 100644 --- a/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue +++ b/app/imports/client/ui/properties/components/attributes/SpellSlotCard.vue @@ -65,7 +65,7 @@ export default { slotId, ritual, scope: { - $attackAdvantage: advantage, + '~attackAdvantage': { value: advantage }, }, }, error => { if (!error) return; diff --git a/app/imports/client/ui/properties/components/skills/SkillListTile.vue b/app/imports/client/ui/properties/components/skills/SkillListTile.vue index ec436241..d10c4a78 100644 --- a/app/imports/client/ui/properties/components/skills/SkillListTile.vue +++ b/app/imports/client/ui/properties/components/skills/SkillListTile.vue @@ -111,7 +111,7 @@ export default { doCheck.call({ propId: this.model._id, scope: { - $checkAdvantage: advantage, + '~checkAdvantage': { value: advantage }, }, }, error => { this.checkLoading = false; diff --git a/app/imports/client/ui/properties/viewers/ActionViewer.vue b/app/imports/client/ui/properties/viewers/ActionViewer.vue index b76c1b4c..fb989d21 100644 --- a/app/imports/client/ui/properties/viewers/ActionViewer.vue +++ b/app/imports/client/ui/properties/viewers/ActionViewer.vue @@ -197,7 +197,7 @@ export default { slotId, ritual, scope: { - $attackAdvantage: advantage, + '~attackAdvantage': { value: advantage }, }, }, error => { if (!error) return; From 3acf42394d021e0d40b946e7834d5c033a769f62 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 13:50:14 +0200 Subject: [PATCH 078/117] Fixed errors thrown when overloading discord webhooks --- app/imports/api/creature/log/CreatureLogs.js | 26 ++++++++++++++++--- .../components/actions/ActionCard.vue | 2 +- app/imports/server/discord/sendWebhook.js | 13 +++++++--- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/app/imports/api/creature/log/CreatureLogs.js b/app/imports/api/creature/log/CreatureLogs.js index 5b858b29..8d21bd3d 100644 --- a/app/imports/api/creature/log/CreatureLogs.js +++ b/app/imports/api/creature/log/CreatureLogs.js @@ -7,9 +7,10 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; import { parse, prettifyParseError } from '/imports/parser/parser.js'; import resolve, { toString } from '/imports/parser/resolve.js'; -const PER_CREATURE_LOG_LIMIT = 100; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +const PER_CREATURE_LOG_LIMIT = 100; + if (Meteor.isServer) { var sendWebhookAsCreature = require('/imports/server/discord/sendWebhook.js').sendWebhookAsCreature; } @@ -70,10 +71,21 @@ function logToMessageData(log) { let embed = { fields: [], }; - log.content.forEach(field => { + log.content.forEach((field, index) => { + // Empty character for blank names if (!field.name) field.name = '\u200b'; if (!field.value) field.value = '\u200b'; - embed.fields.push(field); + // Enforce Discord field character limits + if (field.name.length > 256) { + field.name = field.name.substring(0, 255); + } + if (field.value.length > 1024) { + field.value = field.value.substring(0, 1024 - 3) + '...'; + } + // Enforce Discord 25 field limit + if (index < 25) { + embed.fields.push(field); + } }); return { embeds: [embed] }; } @@ -122,7 +134,15 @@ export function insertCreatureLogWork({ log, creature, method }) { log = { content: [{ value: log }] }; } if (!log.content?.length) return; + + // Truncate the string lengths to fit the log content schema + log.content.forEach((logItem) => { + if (logItem.value.length > STORAGE_LIMITS.summary) { + logItem.value = logItem.value.substring(0, STORAGE_LIMITS.summary - 3) + '...'; + } + }); log.date = new Date(); + // Insert it let id = CreatureLogs.insert(log); if (Meteor.isServer) { diff --git a/app/imports/client/ui/properties/components/actions/ActionCard.vue b/app/imports/client/ui/properties/components/actions/ActionCard.vue index 55553f48..9565868f 100644 --- a/app/imports/client/ui/properties/components/actions/ActionCard.vue +++ b/app/imports/client/ui/properties/components/actions/ActionCard.vue @@ -217,7 +217,7 @@ export default { this.doActionLoading = false; if (error) { console.error(error); - snackbar({ text: error.reason }); + snackbar({ text: error.reason || error.message || error.toString() }); } }); }, diff --git a/app/imports/server/discord/sendWebhook.js b/app/imports/server/discord/sendWebhook.js index 044de8c3..a8a473df 100644 --- a/app/imports/server/discord/sendWebhook.js +++ b/app/imports/server/discord/sendWebhook.js @@ -1,5 +1,5 @@ import Discord from 'discord.js' -export default function sendWebhook({webhookURL, data = {}}){ +export default function sendWebhook({ webhookURL, data = {} }) { //webhookURL = https://discordapp.com/api/webhooks// let urlArray = webhookURL.split('/'); let token = urlArray.pop(); @@ -9,11 +9,16 @@ export default function sendWebhook({webhookURL, data = {}}){ data.disableMentions = 'all'; const hook = new Discord.WebhookClient(id, token); - // Send a message using the webhook - hook.send(data); + try { + // Send a message using the webhook + hook.send(data); + } catch (e) { + // Swallow the error, we don't really care + console.error(e); + } } -export function sendWebhookAsCreature({creature, data = {}}){ +export function sendWebhookAsCreature({ creature, data = {} }) { if (!creature || !creature.settings || !creature.settings.discordWebhook) return; data.username = creature.name; data.avatarURL = creature.avatarPicture; From 6e47395327f60fefd963b73560e9f9944bc809ba Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 13:52:21 +0200 Subject: [PATCH 079/117] Fixed some error text not showing in create dialog --- app/imports/client/ui/properties/PropertyForm.vue | 12 ++++++++++++ .../client/ui/properties/forms/ActionForm.vue | 4 ++-- .../client/ui/properties/forms/AttributeForm.vue | 2 +- app/imports/client/ui/properties/forms/BuffForm.vue | 2 +- app/imports/client/ui/properties/forms/ClassForm.vue | 2 +- .../client/ui/properties/forms/ClassLevelForm.vue | 2 +- .../client/ui/properties/forms/ContainerForm.vue | 2 +- .../client/ui/properties/forms/FeatureForm.vue | 4 ++-- app/imports/client/ui/properties/forms/ItemForm.vue | 2 +- app/imports/client/ui/properties/forms/NoteForm.vue | 4 ++-- app/imports/client/ui/properties/forms/SkillForm.vue | 2 +- .../client/ui/properties/forms/SlotFillerForm.vue | 2 +- app/imports/client/ui/properties/forms/SlotForm.vue | 2 +- app/imports/client/ui/properties/forms/SpellForm.vue | 4 ++-- .../client/ui/properties/forms/SpellListForm.vue | 2 +- .../client/ui/properties/forms/TriggerForm.vue | 2 +- 16 files changed, 31 insertions(+), 19 deletions(-) diff --git a/app/imports/client/ui/properties/PropertyForm.vue b/app/imports/client/ui/properties/PropertyForm.vue index 75c7ed1c..1852494a 100644 --- a/app/imports/client/ui/properties/PropertyForm.vue +++ b/app/imports/client/ui/properties/PropertyForm.vue @@ -12,6 +12,7 @@ label="Name" style="flex-basis: 320px;" :value="model.name" + :error-messages="errors.name" @change="(value, ack) => $emit('change', {path: ['name'], value, ack})" />
@@ -60,6 +63,7 @@ min="0" hint="How many properties this counts as when filling a slot" :value="model.slotQuantityFilled" + :error-messages="errors.slotQuantityFilled" @change="(value, ack) => $emit('change', {path: ['slotQuantityFilled'], value, ack})" /> @@ -73,6 +77,7 @@ hint="A caclulation to determine if this property can be added to a character" placeholder="Always active" :value="model.slotFillerCondition" + :error-messages="errors.slotFillerCondition" @change="(value, ack) => $emit('change', {path: ['slotFillerCondition'], value, ack})" /> @@ -86,6 +91,7 @@ hint="Text to display if the condition isn't met" placeholder="Always active" :value="model.slotFillerConditionNote" + :error-messages="errors.slotFillerConditionNote" @change="(value, ack) => $emit('change', {path: ['slotFillerConditionNote'], value, ack})" /> @@ -99,6 +105,7 @@ deletable-chips hint="Used to let slots find this property in a library" :value="model.libraryTags" + :error-messages="errors.libraryTags" @change="(value, ack) => $emit('change', {path: ['libraryTags'], value, ack})" /> @@ -119,6 +126,7 @@ deletable-chips hint="Tags let other properties target this property with interactions" :value="model.tags" + :error-messages="errors.tags" @change="(value, ack) => $emit('change', {path: ['tags'], value, ack})" /> @@ -229,6 +237,10 @@ export default { type: String, default: 'creatureProperties' }, + errors: { + type: Object, + default: () => ({}), + }, embedded: Boolean, // This dialog is embedded in a page noChildInsert: Boolean, // Don't allow inserting of children in this form }, diff --git a/app/imports/client/ui/properties/forms/ActionForm.vue b/app/imports/client/ui/properties/forms/ActionForm.vue index 33137670..1b89930e 100644 --- a/app/imports/client/ui/properties/forms/ActionForm.vue +++ b/app/imports/client/ui/properties/forms/ActionForm.vue @@ -79,7 +79,7 @@ label="Summary" hint="This will appear in the action card in the character sheet, summarize what the action does" :model="model.summary" - :error-messages="errors.summary" + :error-messages="errors['summary.text']" @change="({path, value, ack}) => $emit('change', {path: ['summary', ...path], value, ack})" /> @@ -88,7 +88,7 @@ label="Description" hint="This text will be displayed in the log when the action is taken" :model="model.description" - :error-messages="errors.description" + :error-messages="errors['description.text']" @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> diff --git a/app/imports/client/ui/properties/forms/AttributeForm.vue b/app/imports/client/ui/properties/forms/AttributeForm.vue index 0d5e5f33..02f53aaa 100644 --- a/app/imports/client/ui/properties/forms/AttributeForm.vue +++ b/app/imports/client/ui/properties/forms/AttributeForm.vue @@ -70,7 +70,7 @@ diff --git a/app/imports/client/ui/properties/forms/BuffForm.vue b/app/imports/client/ui/properties/forms/BuffForm.vue index 95d63e0a..38e19afa 100644 --- a/app/imports/client/ui/properties/forms/BuffForm.vue +++ b/app/imports/client/ui/properties/forms/BuffForm.vue @@ -3,7 +3,7 @@ diff --git a/app/imports/client/ui/properties/forms/ClassForm.vue b/app/imports/client/ui/properties/forms/ClassForm.vue index c75a871b..08c8b5fb 100644 --- a/app/imports/client/ui/properties/forms/ClassForm.vue +++ b/app/imports/client/ui/properties/forms/ClassForm.vue @@ -17,7 +17,7 @@ diff --git a/app/imports/client/ui/properties/forms/ClassLevelForm.vue b/app/imports/client/ui/properties/forms/ClassLevelForm.vue index bb4d630f..5ee2f04b 100644 --- a/app/imports/client/ui/properties/forms/ClassLevelForm.vue +++ b/app/imports/client/ui/properties/forms/ClassLevelForm.vue @@ -33,7 +33,7 @@ label="Description" hint="A brief description of what this class level gives a character" :model="model.description" - :error-messages="errors.description" + :error-messages="errors['description.text']" @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> diff --git a/app/imports/client/ui/properties/forms/ContainerForm.vue b/app/imports/client/ui/properties/forms/ContainerForm.vue index 6af54725..71e4ccec 100644 --- a/app/imports/client/ui/properties/forms/ContainerForm.vue +++ b/app/imports/client/ui/properties/forms/ContainerForm.vue @@ -66,7 +66,7 @@ class="mt-4" label="Description" :model="model.description" - :error-messages="errors.description" + :error-messages="errors['description.text']" @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> diff --git a/app/imports/client/ui/properties/forms/FeatureForm.vue b/app/imports/client/ui/properties/forms/FeatureForm.vue index 9e814976..fbde4df8 100644 --- a/app/imports/client/ui/properties/forms/FeatureForm.vue +++ b/app/imports/client/ui/properties/forms/FeatureForm.vue @@ -4,7 +4,7 @@ label="Summary" hint="This will appear in the feature card in the character sheet" :model="model.summary" - :error-messages="errors.summary" + :error-messages="errors['summary.text']" @change="({path, value, ack}) => $emit('change', {path: ['summary', ...path], value, ack})" /> @@ -13,7 +13,7 @@ label="Description" hint="The rest of the description that doesn't fit in the summary goes here" :model="model.description" - :error-messages="errors.description" + :error-messages="errors['description.text']" @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> diff --git a/app/imports/client/ui/properties/forms/ItemForm.vue b/app/imports/client/ui/properties/forms/ItemForm.vue index 74899c8a..94182d0e 100644 --- a/app/imports/client/ui/properties/forms/ItemForm.vue +++ b/app/imports/client/ui/properties/forms/ItemForm.vue @@ -75,7 +75,7 @@ diff --git a/app/imports/client/ui/properties/forms/NoteForm.vue b/app/imports/client/ui/properties/forms/NoteForm.vue index f0e9456d..0d14aabe 100644 --- a/app/imports/client/ui/properties/forms/NoteForm.vue +++ b/app/imports/client/ui/properties/forms/NoteForm.vue @@ -4,7 +4,7 @@ label="Summary" hint="This will appear in the card in the character sheet" :model="model.summary" - :error-messages="errors.summary" + :error-messages="errors['summary.text']" @change="({path, value, ack}) => $emit('change', {path: ['summary', ...path], value, ack})" /> @@ -13,7 +13,7 @@ label="Description" hint="Text that does not fit in the summary" :model="model.description" - :error-messages="errors.description" + :error-messages="errors['description.text']" @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> diff --git a/app/imports/client/ui/properties/forms/SkillForm.vue b/app/imports/client/ui/properties/forms/SkillForm.vue index 4126cfed..d33d0613 100644 --- a/app/imports/client/ui/properties/forms/SkillForm.vue +++ b/app/imports/client/ui/properties/forms/SkillForm.vue @@ -47,7 +47,7 @@ diff --git a/app/imports/client/ui/properties/forms/SlotFillerForm.vue b/app/imports/client/ui/properties/forms/SlotFillerForm.vue index fbf5299c..daa8eb98 100644 --- a/app/imports/client/ui/properties/forms/SlotFillerForm.vue +++ b/app/imports/client/ui/properties/forms/SlotFillerForm.vue @@ -3,7 +3,7 @@ diff --git a/app/imports/client/ui/properties/forms/SpellForm.vue b/app/imports/client/ui/properties/forms/SpellForm.vue index 91df8045..c09e66c7 100644 --- a/app/imports/client/ui/properties/forms/SpellForm.vue +++ b/app/imports/client/ui/properties/forms/SpellForm.vue @@ -220,14 +220,14 @@ label="Summary" hint="This will appear in the action card in the character sheet, summarise what the action does" :model="model.summary" - :error-messages="errors.summary" + :error-messages="errors['summary.text']" @change="({path, value, ack}) => $emit('change', {path: ['summary', ...path], value, ack})" /> diff --git a/app/imports/client/ui/properties/forms/SpellListForm.vue b/app/imports/client/ui/properties/forms/SpellListForm.vue index a082f647..d6413189 100644 --- a/app/imports/client/ui/properties/forms/SpellListForm.vue +++ b/app/imports/client/ui/properties/forms/SpellListForm.vue @@ -58,7 +58,7 @@ diff --git a/app/imports/client/ui/properties/forms/TriggerForm.vue b/app/imports/client/ui/properties/forms/TriggerForm.vue index ece57be5..254f59c7 100644 --- a/app/imports/client/ui/properties/forms/TriggerForm.vue +++ b/app/imports/client/ui/properties/forms/TriggerForm.vue @@ -77,7 +77,7 @@ label="Description" hint="The rest of the description that doesn't fit in the summary goes here" :model="model.description" - :error-messages="errors.description" + :error-messages="errors['description.text']" @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> From c00e618f8524b739c9c02e0c3e02824063ab741a Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:19:06 +0200 Subject: [PATCH 080/117] Added library node "searchable" switch --- .../methods/getSlotFillFilter.js | 1 + .../client/ui/properties/PropertyForm.vue | 22 +++++++++++++++++++ app/imports/server/publications/library.js | 1 + .../server/publications/searchLibraryNodes.js | 3 +-- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js index 9ac41b4e..9558ff55 100644 --- a/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js +++ b/app/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js @@ -4,6 +4,7 @@ export default function getSlotFillFilter({ slot, libraryIds }) { if (!libraryIds) throw 'LibraryIds is required for getSlotFillFilter'; let filter = { + fillSlots: true, removed: { $ne: true }, $and: [] }; diff --git a/app/imports/client/ui/properties/PropertyForm.vue b/app/imports/client/ui/properties/PropertyForm.vue index 1852494a..f108e593 100644 --- a/app/imports/client/ui/properties/PropertyForm.vue +++ b/app/imports/client/ui/properties/PropertyForm.vue @@ -38,6 +38,28 @@ v-if="context.isLibraryForm" dense > + + + + + + Date: Wed, 7 Jun 2023 14:19:31 +0200 Subject: [PATCH 081/117] Made sure atts respect damage rules on recalc --- .../computeVariable/computeVariableAsAttribute.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js index 2e518127..395619a9 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js @@ -4,6 +4,18 @@ export default function computeVariableAsAttribute(computation, node, prop) { let result = getAggregatorResult(node) || 0; prop.total = result; + + // Apply damage in a way that respects the damage rules, modifying damage if need be + // Bound the damage + if (!prop.ignoreLowerLimit && prop.damage > prop.total) { + console.log(`reducing damage from ${prop.damage} to ${prop.total}`); + prop.damage = prop.total; + } + if (!prop.ignoreUpperLimit && prop.damage < 0) { + console.log(`increasing damage from ${prop.damage} to 0`); + prop.damage = 0; + } + // Apply damage prop.value = prop.total - (prop.damage || 0); // Proficiency From e49dea469f6007f605bc77a8be2cb457908e8972 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:25:28 +0200 Subject: [PATCH 082/117] Fixed bug where buff would delete parent prop in group card view --- .../client/ui/properties/components/buffs/BuffListItem.vue | 2 +- .../client/ui/properties/components/folders/FolderGroupCard.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/imports/client/ui/properties/components/buffs/BuffListItem.vue b/app/imports/client/ui/properties/components/buffs/BuffListItem.vue index b122f33c..ef2cb391 100644 --- a/app/imports/client/ui/properties/components/buffs/BuffListItem.vue +++ b/app/imports/client/ui/properties/components/buffs/BuffListItem.vue @@ -10,7 +10,7 @@ mdi-delete diff --git a/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue b/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue index 1784a60a..72f29b4f 100644 --- a/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue +++ b/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue @@ -17,7 +17,7 @@ @click="$emit('click-property', {_id: prop._id})" @click-property="e => $emit('click-property', e)" @sub-click="e => $emit('sub-click', e)" - @remove="$emit('remove', prop._id)" + @remove="id => $emit('remove', id || prop._id)" /> From f7461f40d6be0a11c3f267bb4b94b9ed3c425ebd Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:35:42 +0200 Subject: [PATCH 083/117] Fixed calculated toggles not hiding some props from the sheet --- .../ui/creature/character/characterSheetTabs/InventoryTab.vue | 1 + .../ui/creature/character/characterSheetTabs/StatsTab.vue | 3 ++- .../character/printedCharacterSheet/PrintedInventory.vue | 2 ++ .../creature/character/printedCharacterSheet/PrintedStats.vue | 1 + .../ui/properties/components/folders/FolderGroupCard.vue | 1 + .../folders/folderGroupComponents/FolderGroupChildren.vue | 1 + .../ui/properties/components/inventory/ContainerCard.vue | 1 + 7 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue index b87f2dd9..426aa61a 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/InventoryTab.vue @@ -198,6 +198,7 @@ export default { equipped: { $ne: true }, removed: { $ne: true }, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, }, { sort: { order: 1 }, }); diff --git a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue index a5fd932a..39c21a70 100644 --- a/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue +++ b/app/imports/client/ui/creature/character/characterSheetTabs/StatsTab.vue @@ -456,7 +456,7 @@ const propertyHandlers = { }, toggle(prop) { if ( - prop.deactivatedByAncestor || !prop.showUI + prop.deactivatedByToggle || prop.deactivatedByAncestor || !prop.showUI ) return { propPath: null }; return { propPath: 'toggle' }; }, @@ -580,6 +580,7 @@ export default { 'ancestors.id': this.creatureId, removed: { $ne: true }, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, showUI: true, }, { sort: { order: 1 } diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue index 324b64fa..416e8b00 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedInventory.vue @@ -152,6 +152,7 @@ export default { removed: { $ne: true }, equipped: { $ne: true }, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, }, { sort: { order: 1 }, }).fetch(); @@ -168,6 +169,7 @@ export default { equipped: { $ne: true }, removed: { $ne: true }, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, }, { sort: { order: 1 }, }); diff --git a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue index 18069fc2..039209e2 100644 --- a/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue +++ b/app/imports/client/ui/creature/character/printedCharacterSheet/PrintedStats.vue @@ -450,6 +450,7 @@ export default { type: 'toggle', removed: { $ne: true }, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, showUI: true, }, { sort: { order: 1 } diff --git a/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue b/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue index 72f29b4f..b21a1a2b 100644 --- a/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue +++ b/app/imports/client/ui/properties/components/folders/FolderGroupCard.vue @@ -49,6 +49,7 @@ export default { type: 'toggle', showUI: true, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, }, { type: { $ne: 'toggle' }, diff --git a/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue b/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue index 32edef29..bbb026b5 100644 --- a/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue +++ b/app/imports/client/ui/properties/components/folders/folderGroupComponents/FolderGroupChildren.vue @@ -42,6 +42,7 @@ export default { type: 'toggle', showUI: true, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, }, { type: { $ne: 'toggle' }, diff --git a/app/imports/client/ui/properties/components/inventory/ContainerCard.vue b/app/imports/client/ui/properties/components/inventory/ContainerCard.vue index b2d6de9f..fee97c5b 100644 --- a/app/imports/client/ui/properties/components/inventory/ContainerCard.vue +++ b/app/imports/client/ui/properties/components/inventory/ContainerCard.vue @@ -99,6 +99,7 @@ export default { removed: { $ne: true }, equipped: { $ne: true }, deactivatedByAncestor: { $ne: true }, + deactivatedByToggle: { $ne: true }, }, { sort: { order: 1 }, }); From ea02416353dc3b1f836cf5cc70f7296f05099013 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:43:01 +0200 Subject: [PATCH 084/117] Fixed critical hit target changing --- .../api/engine/actions/applyPropertyByType/applyAction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js index ff2f9024..560e0bba 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyAction.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyAction.js @@ -171,7 +171,7 @@ function rollAttack(attack, scope) { } function applyCrits(value, scope) { - const criticalHitTarget = 20; // scope['~criticalHitTarget']?.value || 20; + const criticalHitTarget = scope['~criticalHitTarget']?.value || 20; let criticalHit = value >= criticalHitTarget; let criticalMiss; if (criticalHit) { From 60172f8a31b11e606edb90eabff407260f411119 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:43:20 +0200 Subject: [PATCH 085/117] Fixed errors logging when some fields aren't used --- app/imports/api/creature/log/CreatureLogs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/imports/api/creature/log/CreatureLogs.js b/app/imports/api/creature/log/CreatureLogs.js index 8d21bd3d..62fbf798 100644 --- a/app/imports/api/creature/log/CreatureLogs.js +++ b/app/imports/api/creature/log/CreatureLogs.js @@ -76,10 +76,10 @@ function logToMessageData(log) { if (!field.name) field.name = '\u200b'; if (!field.value) field.value = '\u200b'; // Enforce Discord field character limits - if (field.name.length > 256) { + if (field.name?.length > 256) { field.name = field.name.substring(0, 255); } - if (field.value.length > 1024) { + if (field.value?.length > 1024) { field.value = field.value.substring(0, 1024 - 3) + '...'; } // Enforce Discord 25 field limit @@ -137,7 +137,7 @@ export function insertCreatureLogWork({ log, creature, method }) { // Truncate the string lengths to fit the log content schema log.content.forEach((logItem) => { - if (logItem.value.length > STORAGE_LIMITS.summary) { + if (logItem.value?.length > STORAGE_LIMITS.summary) { logItem.value = logItem.value.substring(0, STORAGE_LIMITS.summary - 3) + '...'; } }); From 26affda3399fba3733848d63a0039195db0d5d8e Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:51:53 +0200 Subject: [PATCH 086/117] Fixed rest triggered buffs not recalculating sheet --- .../api/engine/actions/applyPropertyByType/applyBuff.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js index 4ee2cd92..3e48f057 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applyBuff.js @@ -21,7 +21,10 @@ export default function applyBuff(node, actionContext) { const prop = node.node; let buffTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets; - // Then copy the decendants of the buff to the targets + // Mark the buff as dirty for recalculation + prop.dirty = true; + + // Then copy the descendants of the buff to the targets let propList = [prop]; function addChildrenToPropList(children, { skipCrystalize } = {}) { children.forEach(child => { From af421eef9ced211741ac698958e248f7dd1f061f Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:54:12 +0200 Subject: [PATCH 087/117] Removed references to DiceCloud being in beta --- app/imports/client/ui/pages/NotImplemented.vue | 2 +- app/imports/client/ui/sharing/ShareDialog.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/imports/client/ui/pages/NotImplemented.vue b/app/imports/client/ui/pages/NotImplemented.vue index 87558758..e3a3c471 100644 --- a/app/imports/client/ui/pages/NotImplemented.vue +++ b/app/imports/client/ui/pages/NotImplemented.vue @@ -4,7 +4,7 @@ :value="true" type="info" > - This page is not available in this version of the beta. + This page is not available in this version of DiceCloud. diff --git a/app/imports/client/ui/sharing/ShareDialog.vue b/app/imports/client/ui/sharing/ShareDialog.vue index 7db4aa24..5b81c469 100644 --- a/app/imports/client/ui/sharing/ShareDialog.vue +++ b/app/imports/client/ui/sharing/ShareDialog.vue @@ -27,7 +27,7 @@ v-if="model.public && docRef.collection === 'libraries'" readonly label="Link" - :value="'https://beta.dicecloud.com' + $router.resolve({ + :value="'https://dicecloud.com' + $router.resolve({ name: 'singleLibrary', params: { id: model._id }, }).href" From 3950db86722e91453e01807c35ecd672e68713a7 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 14:58:32 +0200 Subject: [PATCH 088/117] Passive bonus now gets +-5 with adv/disadvantage --- .../computeVariable/computeVariableAsSkill.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js index b13ac43b..336a5ab7 100644 --- a/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js +++ b/app/imports/api/engine/computation/computeComputation/computeByType/computeVariable/computeVariableAsSkill.js @@ -66,6 +66,18 @@ export default function computeVariableAsSkill(computation, node, prop) { } // Passive bonus prop.passiveBonus = aggregator.passiveAdd; + // +/- 5 to passive bonus if the skill has advantage/disadvantage + if ( + prop.advantage === 1 + && Number.isFinite(prop.passiveBonus) + ) { + prop.passiveBonus += 5; + } else if ( + prop.advantage === -1 + && Number.isFinite(prop.passiveBonus) + ) { + prop.bassiveBonus -= 5; + } // conditional benefits prop.conditionalBenefits = aggregator.conditional; // Roll bonuses From f66190463a01d38be92651e1d40e45be411a1c0e Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 15:01:00 +0200 Subject: [PATCH 089/117] Fixed spell lists w/ no max prepared can't prepare --- .../client/ui/properties/components/spells/SpellListCard.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/imports/client/ui/properties/components/spells/SpellListCard.vue b/app/imports/client/ui/properties/components/spells/SpellListCard.vue index d2d3134b..c31e5897 100644 --- a/app/imports/client/ui/properties/components/spells/SpellListCard.vue +++ b/app/imports/client/ui/properties/components/spells/SpellListCard.vue @@ -15,7 +15,7 @@ v-if="preparingSpells || preparedError" :class="{'error--text' : preparedError}" > - {{ numPrepared }}/{{ model.maxPrepared.value }} spells prepared + {{ numPrepared }}/{{ model.maxPrepared && model.maxPrepared.value || 0 }} spells prepared Date: Wed, 7 Jun 2023 15:06:28 +0200 Subject: [PATCH 090/117] Removed "hide when full" switch from slots It currently doesn't do anything in this iteration of slot UI --- app/imports/client/ui/properties/forms/SlotForm.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/imports/client/ui/properties/forms/SlotForm.vue b/app/imports/client/ui/properties/forms/SlotForm.vue index 4dfcf386..88a7467f 100644 --- a/app/imports/client/ui/properties/forms/SlotForm.vue +++ b/app/imports/client/ui/properties/forms/SlotForm.vue @@ -76,6 +76,7 @@ /> @@ -84,7 +85,6 @@ data-id="test-slot-button" > + Date: Wed, 7 Jun 2023 15:11:35 +0200 Subject: [PATCH 091/117] Fixed folders in same location being out of tree order --- .../ui/properties/components/folders/tabFoldersMixin.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js b/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js index 5097143e..33dd5105 100644 --- a/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js +++ b/app/imports/client/ui/properties/components/folders/tabFoldersMixin.js @@ -11,6 +11,10 @@ function getFolders(creatureId, tab, location) { removed: { $ne: true }, tab, location, + }, { + sort: { + order: 1, + } }); } From 9ae8d63fc4f934c6130c0d6d0c43c9a0da94e291 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 7 Jun 2023 15:20:04 +0200 Subject: [PATCH 092/117] Fixed, saving throw changing target of later props the target will only be changed for the children of the save --- .../engine/actions/applyPropertyByType/applySavingThrow.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js index 2e2133e8..e0803109 100644 --- a/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js +++ b/app/imports/api/engine/actions/applyPropertyByType/applySavingThrow.js @@ -8,6 +8,7 @@ import { applyUnresolvedEffects } from '/imports/api/engine/actions/doCheck.js'; export default function applySavingThrow(node, actionContext) { applyNodeTriggers(node, 'before', actionContext); const prop = node.node; + const originalTargets = actionContext.targets; let saveTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets; @@ -45,8 +46,8 @@ export default function applySavingThrow(node, actionContext) { delete scope['~saveRoll']; const applyChildren = function () { - applyNodeTriggers(node, 'after', actionContext); actionContext.targets = [target] + applyNodeTriggers(node, 'after', actionContext); node.children.forEach(child => applyProperty(child, actionContext)); }; @@ -106,4 +107,6 @@ export default function applySavingThrow(node, actionContext) { }); return applyChildren(); }); + // reset the targets after the save to each child + actionContext.targets = originalTargets; } From c314c0ab0554b2c8aa3b3f98510c7155e3e0e6d9 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Mon, 12 Jun 2023 22:16:20 +0200 Subject: [PATCH 093/117] Added basic community library browser --- app/imports/api/library/Libraries.js | 35 +++- app/imports/api/library/LibraryCollections.js | 24 ++- app/imports/api/users/Users.js | 32 +++- .../client/ui/components/global/SmartBtn.vue | 2 +- .../ui/creature/slots/SlotFillDialog.vue | 28 +++- .../ui/dialogStack/DialogComponentIndex.js | 2 + .../ui/library/LibraryBrowserDialog.vue | 35 ++++ .../library/LibraryCollectionEditDialog.vue | 5 + .../ui/library/LibraryCollectionToolbar.vue | 17 +- .../client/ui/library/LibraryEditDialog.vue | 22 ++- .../ui/library/SingleLibraryToolbar.vue | 17 +- app/imports/client/ui/pages/Library.vue | 9 +- .../client/ui/pages/LibraryBrowser.vue | 153 ++++++++++++++++++ app/imports/client/ui/router.js | 10 ++ .../client/ui/utility/numberFormatter.js | 3 + app/imports/migrations/server/dbv2/dbv2.js | 36 ++++- app/imports/server/publications/library.js | 18 +++ 17 files changed, 423 insertions(+), 25 deletions(-) create mode 100644 app/imports/client/ui/library/LibraryBrowserDialog.vue create mode 100644 app/imports/client/ui/pages/LibraryBrowser.vue create mode 100644 app/imports/client/ui/utility/numberFormatter.js diff --git a/app/imports/api/library/Libraries.js b/app/imports/api/library/Libraries.js index 696f0001..1cbb723e 100644 --- a/app/imports/api/library/Libraries.js +++ b/app/imports/api/library/Libraries.js @@ -29,6 +29,16 @@ let LibrarySchema = new SimpleSchema({ optional: true, max: STORAGE_LIMITS.summary, }, + showInMarket: { + index: 1, + type: Boolean, + optional: true, + }, + subscriberCount: { + index: 1, + type: Number, + optional: true, + }, }); LibrarySchema.extend(SharingSchema); @@ -104,6 +114,29 @@ const updateLibraryDescription = new ValidatedMethod({ }, }); +const updateLibraryShowInMarket = new ValidatedMethod({ + name: 'libraries.updateShowInMarket', + validate: new SimpleSchema({ + _id: { + type: String, + regEx: SimpleSchema.RegEx.id + }, + value: { + type: Boolean, + }, + }).validator(), + mixins: [RateLimiterMixin], + rateLimit: { + numRequests: 5, + timeInterval: 5000, + }, + run({ _id, value }) { + let library = Libraries.findOne(_id); + assertEditPermission(library, this.userId); + Libraries.update(_id, { $set: { showInMarket: value } }); + }, +}); + const removeLibrary = new ValidatedMethod({ name: 'libraries.remove', validate: new SimpleSchema({ @@ -130,4 +163,4 @@ export function removeLibaryWork(libraryId) { LibraryNodes.remove({ 'ancestors.id': libraryId }); } -export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, removeLibrary }; +export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, updateLibraryShowInMarket, removeLibrary }; diff --git a/app/imports/api/library/LibraryCollections.js b/app/imports/api/library/LibraryCollections.js index aec4d351..aff9fe20 100644 --- a/app/imports/api/library/LibraryCollections.js +++ b/app/imports/api/library/LibraryCollections.js @@ -32,6 +32,16 @@ const LibraryCollectionSchema = new SimpleSchema({ type: String, regEx: SimpleSchema.RegEx.Id, }, + showInMarket: { + index: 1, + type: Boolean, + optional: true, + }, + subscriberCount: { + index: 1, + type: Number, + optional: true, + }, }); LibraryCollectionSchema.extend(SharingSchema); @@ -48,12 +58,12 @@ const insertLibraryCollection = new ValidatedMethod({ run(libraryCollection) { if (!this.userId) { throw new Meteor.Error('LibraryCollections.methods.insert.denied', - 'You need to be logged in to insert a library'); + 'You need to be logged in to insert a library'); } let tier = getUserTier(this.userId); - if (!tier.paidBenefits){ + if (!tier.paidBenefits) { throw new Meteor.Error('LibraryCollections.methods.insert.denied', - `The ${tier.name} tier does not allow you to insert a library collection`); + `The ${tier.name} tier does not allow you to insert a library collection`); } libraryCollection.owner = this.userId; return LibraryCollections.insert(libraryCollection); @@ -72,7 +82,7 @@ const updateLibraryCollection = new ValidatedMethod({ }, update: { type: LibraryCollectionSchema - .pick('name', 'description', 'libraries') + .pick('name', 'description', 'libraries', 'showInMarket') .extend({ //make libraries optional libraries: { optional: true, @@ -85,7 +95,7 @@ const updateLibraryCollection = new ValidatedMethod({ numRequests: 5, timeInterval: 5000, }, - run({_id, update}){ + run({ _id, update }) { const libraryCollection = LibraryCollections.findOne(_id, { fields: { owner: 1, @@ -93,7 +103,7 @@ const updateLibraryCollection = new ValidatedMethod({ } }); assertEditPermission(libraryCollection, this.userId); - return LibraryCollections.update(_id, {$set: update}); + return LibraryCollections.update(_id, { $set: update }); }, }); @@ -110,7 +120,7 @@ const removeLibraryCollection = new ValidatedMethod({ numRequests: 5, timeInterval: 5000, }, - run({_id}){ + run({ _id }) { const libraryCollection = LibraryCollections.findOne(_id, { fields: { owner: 1, diff --git a/app/imports/api/users/Users.js b/app/imports/api/users/Users.js index 54328914..47f1f25d 100644 --- a/app/imports/api/users/Users.js +++ b/app/imports/api/users/Users.js @@ -1,11 +1,12 @@ import SimpleSchema from 'simpl-schema'; import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; +import Libraries from '/imports/api/library/Libraries.js'; +import LibraryCollections from '/imports/api/library/LibraryCollections.js'; import '/imports/api/users/methods/deleteMyAccount.js'; import '/imports/api/users/methods/addEmail.js'; import '/imports/api/users/methods/removeEmail.js'; import '/imports/api/users/methods/updateFileStorageUsed.js'; - import { some } from 'lodash'; const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LIBRARIES.split(',') || []; const defaultLibraryCollections = process.env.DEFAULT_LIBRARY_COLLECTIONS && process.env.DEFAULT_LIBRARY_COLLECTIONS.split(',') || []; @@ -250,6 +251,29 @@ Meteor.users.setPreference = new ValidatedMethod({ }, }); +if (Meteor.isServer) { + Accounts.onCreateUser(() => { + if (defaultLibraries?.length) { + Libraries.update({ + _id: { $in: defaultLibraries } + }, { + $inc: { subscriberCount: 1 } + }, { + multi: true, + }, () => {/**/ }); + } + if (defaultLibraryCollections?.length) { + LibraryCollections.update({ + _id: { $in: defaultLibraryCollections } + }, { + $inc: { subscriberCount: 1 } + }, { + multi: true, + }, () => {/**/ }); + } + }); +} + Meteor.users.subscribeToLibrary = new ValidatedMethod({ name: 'users.subscribeToLibrary', validate: new SimpleSchema({ @@ -264,15 +288,17 @@ Meteor.users.subscribeToLibrary = new ValidatedMethod({ mixins: [RateLimiterMixin], rateLimit: { numRequests: 5, - timeInterval: 5000, + timeInterval: 2000, }, run({ libraryId, subscribe }) { if (!this.userId) throw 'Can only subscribe if logged in'; if (subscribe) { + Libraries.update({ _id: libraryId }, { $inc: { subscriberCount: 1 } }, () => {/**/ }); return Meteor.users.update(this.userId, { $addToSet: { subscribedLibraries: libraryId }, }); } else { + Libraries.update({ _id: libraryId }, { $inc: { subscriberCount: -1 } }, () => {/**/ }); return Meteor.users.update(this.userId, { $pullAll: { subscribedLibraries: libraryId }, }); @@ -299,10 +325,12 @@ Meteor.users.subscribeToLibraryCollection = new ValidatedMethod({ run({ libraryCollectionId, subscribe }) { if (!this.userId) throw 'Can only subscribe if logged in'; if (subscribe) { + LibraryCollections.update({ _id: libraryCollectionId }, { $inc: { subscriberCount: 1 } }, () => {/**/ }); return Meteor.users.update(this.userId, { $addToSet: { subscribedLibraryCollections: libraryCollectionId }, }); } else { + LibraryCollections.update({ _id: libraryCollectionId }, { $inc: { subscriberCount: -1 } }, () => {/**/ }); return Meteor.users.update(this.userId, { $pullAll: { subscribedLibraryCollections: libraryCollectionId }, }); diff --git a/app/imports/client/ui/components/global/SmartBtn.vue b/app/imports/client/ui/components/global/SmartBtn.vue index e423e22d..c4fb5e03 100644 --- a/app/imports/client/ui/components/global/SmartBtn.vue +++ b/app/imports/client/ui/components/global/SmartBtn.vue @@ -3,7 +3,7 @@ v-bind="$attrs" :disabled="isDisabled" :loading="loading" - @click.stop="click" + @click.stop.prevent="click" > diff --git a/app/imports/client/ui/creature/slots/SlotFillDialog.vue b/app/imports/client/ui/creature/slots/SlotFillDialog.vue index 7eeddf22..75af7420 100644 --- a/app/imports/client/ui/creature/slots/SlotFillDialog.vue +++ b/app/imports/client/ui/creature/slots/SlotFillDialog.vue @@ -147,11 +147,27 @@ + Can't find what you're looking for? + + + + Browse community libraries + - Create custom + Create custom filler @@ -41,6 +49,7 @@ import LibraryCollections from '/imports/api/library/LibraryCollections.js'; import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js'; import { mapMutations } from 'vuex'; +import formatter from '/imports/client/ui/utility/numberFormatter.js'; export default { data() { @@ -87,6 +96,9 @@ export default { ...mapMutations([ 'toggleDrawer', ]), + formatNumber(num) { + return formatter.format(num); + }, subscribe(value) { this.loading = true; Meteor.users.subscribeToLibraryCollection.call({ @@ -103,6 +115,9 @@ export default { data: { _id: this.$route.params.id }, }); }, + back() { + return window.history.length > 2 ? this.$router.back() : this.$router.push('/library'); + }, }, } diff --git a/app/imports/client/ui/library/LibraryEditDialog.vue b/app/imports/client/ui/library/LibraryEditDialog.vue index 36decbbc..29bd535c 100644 --- a/app/imports/client/ui/library/LibraryEditDialog.vue +++ b/app/imports/client/ui/library/LibraryEditDialog.vue @@ -31,6 +31,11 @@ :value="model.description" @change="updateDescription" /> + diff --git a/app/imports/client/ui/creature/creatureProperties/Breadcrumbs.vue b/app/imports/client/ui/creature/creatureProperties/Breadcrumbs.vue index 228e47f2..54703e43 100644 --- a/app/imports/client/ui/creature/creatureProperties/Breadcrumbs.vue +++ b/app/imports/client/ui/creature/creatureProperties/Breadcrumbs.vue @@ -4,11 +4,14 @@ :class="{'no-icons': noIcons}" > - + mdi-account + + mdi-book-open-blank-variant + fetchDocByRef(ref)) - .filter(prop => prop.type !== 'propertySlot'); + .filter(prop => (this.collection !== 'creatureProperties' || prop.type !== 'propertySlot')); }, }, methods: { - click(id){ + click(id) { + if (this.embedded) { + this.$emit('select-sub-property', id); + return; + } const store = this.$store; // Check if there is a dialog open for this doc already let dialogFound; @@ -92,9 +104,12 @@ // Pop dialogs until we get to it store.dispatch('popDialogStacks', dialogsToPop); } else { + const component = this.collection === 'creatureProperties' ? 'creature-property-dialog' + : this.collection === 'libraryNodes' ? 'library-node-dialog' + : undefined; // Otherwise open it as a new dialog store.commit('pushDialogStack', { - component: 'creature-property-dialog', + component, elementId: `breadcrumb-${id}`, data: { _id: id, @@ -139,9 +154,6 @@ .breadcrumbs { margin-bottom: 16px; opacity: 0.8; -} -.no-icons { - } diff --git a/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue b/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue index 606e270b..ce6e3c83 100644 --- a/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue +++ b/app/imports/client/ui/creature/creatureProperties/CreaturePropertyDialog.vue @@ -16,12 +16,12 @@
- + {{ typeName }} @@ -43,31 +43,12 @@ @select-sub-property="selectSubProperty" />
-
- - - - - - -
+
+ +
+ + + + {{ typeName }} + +
+
@@ -41,16 +59,13 @@ @add-child="addLibraryNode" @select-sub-property="selectSubProperty" /> - -

- This property can't be viewed yet. -

- + > + + + + +
+ + {{ tag }} + +
+
+ + + +
+
This property can't be viewed yet.
@@ -13,15 +106,40 @@ diff --git a/app/imports/client/ui/properties/viewers/SlotFillerViewer.vue b/app/imports/client/ui/properties/viewers/SlotFillerViewer.vue index fc53aec7..2e24617e 100644 --- a/app/imports/client/ui/properties/viewers/SlotFillerViewer.vue +++ b/app/imports/client/ui/properties/viewers/SlotFillerViewer.vue @@ -1,32 +1,6 @@ From 442aea2bbe562fc6cde62155c73f4a04b46d843d Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 13 Jun 2023 14:27:32 +0200 Subject: [PATCH 098/117] Forms try to hold your place better Expanding form sections keeps them expanded when changing property viewed or opening a new prop Disabled auto-focus, because it forces scroll --- .../client/ui/properties/PropertyForm.vue | 2 ++ .../client/ui/properties/forms/ActionForm.vue | 2 +- .../ui/properties/forms/AdjustmentForm.vue | 8 +------ .../ui/properties/forms/AttributeForm.vue | 2 +- .../client/ui/properties/forms/BranchForm.vue | 2 +- .../client/ui/properties/forms/BuffForm.vue | 2 +- .../ui/properties/forms/BuffRemoverForm.vue | 2 +- .../client/ui/properties/forms/ClassForm.vue | 2 +- .../ui/properties/forms/ClassLevelForm.vue | 1 + .../ui/properties/forms/ConstantForm.vue | 1 + .../ui/properties/forms/ContainerForm.vue | 5 ++++- .../client/ui/properties/forms/DamageForm.vue | 2 +- .../properties/forms/DamageMultiplierForm.vue | 2 +- .../client/ui/properties/forms/EffectForm.vue | 1 + .../ui/properties/forms/FeatureForm.vue | 1 + .../client/ui/properties/forms/FolderForm.vue | 2 +- .../client/ui/properties/forms/ItemForm.vue | 2 +- .../client/ui/properties/forms/NoteForm.vue | 1 + .../ui/properties/forms/PointBuyForm.vue | 2 +- .../ui/properties/forms/ProficiencyForm.vue | 1 + .../ui/properties/forms/ReferenceForm.vue | 1 + .../client/ui/properties/forms/RollForm.vue | 2 +- .../ui/properties/forms/SavingThrowForm.vue | 2 +- .../client/ui/properties/forms/SkillForm.vue | 2 +- .../ui/properties/forms/SlotFillerForm.vue | 1 + .../client/ui/properties/forms/SlotForm.vue | 9 +------- .../client/ui/properties/forms/SpellForm.vue | 2 +- .../ui/properties/forms/SpellListForm.vue | 1 + .../client/ui/properties/forms/ToggleForm.vue | 2 +- .../ui/properties/forms/TriggerForm.vue | 2 +- .../properties/forms/shared/FormSections.vue | 21 ++++++++++++++++++- app/imports/client/ui/vuexStore.js | 9 +++++++- 32 files changed, 62 insertions(+), 35 deletions(-) diff --git a/app/imports/client/ui/properties/PropertyForm.vue b/app/imports/client/ui/properties/PropertyForm.vue index 003a1fbc..473e49ab 100644 --- a/app/imports/client/ui/properties/PropertyForm.vue +++ b/app/imports/client/ui/properties/PropertyForm.vue @@ -283,6 +283,7 @@ export default { } }, mounted() { + /** Disable auto-focus, it gets in the way more than it helps // Don't autofocus on mobile, it brings up the on-screen keyboard if (this.$vuetify.breakpoint.smAndDown) return; @@ -291,6 +292,7 @@ export default { this.$refs.focusFirst.focus() } }, 300); + */ }, methods: { selectSubProperty(_id){ diff --git a/app/imports/client/ui/properties/forms/ActionForm.vue b/app/imports/client/ui/properties/forms/ActionForm.vue index 1b89930e..218f0c03 100644 --- a/app/imports/client/ui/properties/forms/ActionForm.vue +++ b/app/imports/client/ui/properties/forms/ActionForm.vue @@ -93,7 +93,7 @@ $emit('change', {path: ['description', ...path], value, ack})" /> - + - + - - -
diff --git a/app/imports/client/ui/properties/forms/AttributeForm.vue b/app/imports/client/ui/properties/forms/AttributeForm.vue index 02f53aaa..32072b95 100644 --- a/app/imports/client/ui/properties/forms/AttributeForm.vue +++ b/app/imports/client/ui/properties/forms/AttributeForm.vue @@ -74,7 +74,7 @@ @change="({path, value, ack}) => $emit('change', {path: ['description', ...path], value, ack})" /> - + - + - + - + - + diff --git a/app/imports/client/ui/properties/forms/ConstantForm.vue b/app/imports/client/ui/properties/forms/ConstantForm.vue index 297ff737..316688bd 100644 --- a/app/imports/client/ui/properties/forms/ConstantForm.vue +++ b/app/imports/client/ui/properties/forms/ConstantForm.vue @@ -24,6 +24,7 @@
diff --git a/app/imports/client/ui/properties/forms/ContainerForm.vue b/app/imports/client/ui/properties/forms/ContainerForm.vue index 71e4ccec..9d8556f3 100644 --- a/app/imports/client/ui/properties/forms/ContainerForm.vue +++ b/app/imports/client/ui/properties/forms/ContainerForm.vue @@ -71,7 +71,10 @@ $emit('change', {path: ['description', ...path], value, ack})" /> - + diff --git a/app/imports/client/ui/properties/forms/DamageForm.vue b/app/imports/client/ui/properties/forms/DamageForm.vue index bf7682af..b7ed8881 100644 --- a/app/imports/client/ui/properties/forms/DamageForm.vue +++ b/app/imports/client/ui/properties/forms/DamageForm.vue @@ -42,7 +42,7 @@ :error-messages="errors.target" @change="change('target', ...arguments)" /> - + - + diff --git a/app/imports/client/ui/properties/forms/FeatureForm.vue b/app/imports/client/ui/properties/forms/FeatureForm.vue index fbde4df8..acf86e2c 100644 --- a/app/imports/client/ui/properties/forms/FeatureForm.vue +++ b/app/imports/client/ui/properties/forms/FeatureForm.vue @@ -21,6 +21,7 @@ diff --git a/app/imports/client/ui/properties/forms/FolderForm.vue b/app/imports/client/ui/properties/forms/FolderForm.vue index 0d4e0825..3baab232 100644 --- a/app/imports/client/ui/properties/forms/FolderForm.vue +++ b/app/imports/client/ui/properties/forms/FolderForm.vue @@ -1,7 +1,7 @@