Compare commits
33 Commits
2.0-beta.2
...
2.0-beta.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11a2851ac4 | ||
|
|
313382fb82 | ||
|
|
b9ae337a64 | ||
|
|
4dc0a6159b | ||
|
|
e00dfe1532 | ||
|
|
28e1fcabd5 | ||
|
|
2c0496b44b | ||
|
|
89adda60ec | ||
|
|
8c3710cda3 | ||
|
|
b501b9d830 | ||
|
|
574f8373e7 | ||
|
|
a7ecdecec1 | ||
|
|
0aa59a4bfc | ||
|
|
8f0ff3245e | ||
|
|
9a2d10b7ed | ||
|
|
a8aa1923a8 | ||
|
|
57fa162c89 | ||
|
|
4d548c901c | ||
|
|
a97be2f93a | ||
|
|
1276f872a0 | ||
|
|
7daab97297 | ||
|
|
2e3704d096 | ||
|
|
7283a27727 | ||
|
|
3517636b8b | ||
|
|
e617ef9b75 | ||
|
|
cd45ae1442 | ||
|
|
bcedd548c7 | ||
|
|
dc53e38efe | ||
|
|
e381b3b24d | ||
|
|
111d971bc2 | ||
|
|
bf4ce4f9f7 | ||
|
|
2a983b0a94 | ||
|
|
a5460bba0b |
@@ -13,42 +13,43 @@ export default function applyAdjustment({
|
|||||||
...creature.variables,
|
...creature.variables,
|
||||||
...actionContext,
|
...actionContext,
|
||||||
};
|
};
|
||||||
try {
|
var {result, context} = evaluateString({
|
||||||
var {result, errors} = evaluateString(prop.amount, scope, 'reduce');
|
string: prop.amount,
|
||||||
if (typeof result !== 'number') {
|
scope,
|
||||||
log.content.push({
|
fn: 'reduce'
|
||||||
name: 'Attribute damage',
|
});
|
||||||
error: errors.join(', ') || 'Something went wrong',
|
context.errors.forEach(e => {
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage error',
|
||||||
error: e.toString(),
|
error: e.message || e.toString(),
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
if (damageTargets) {
|
if (damageTargets) {
|
||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
if (prop.target === 'each'){
|
if (prop.target === 'each'){
|
||||||
result = evaluateString(prop.amount, scope, 'reduce');
|
({result} = evaluateString({
|
||||||
|
string: prop.amount,
|
||||||
|
scope,
|
||||||
|
fn: 'reduce'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
damagePropertiesByName.call({
|
damagePropertiesByName.call({
|
||||||
creatureId: target._id,
|
creatureId: target._id,
|
||||||
variableName: prop.stat,
|
variableName: prop.stat,
|
||||||
operation: prop.operation || 'increment',
|
operation: prop.operation || 'increment',
|
||||||
value: result
|
value: result.value,
|
||||||
});
|
});
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
||||||
result: `${-result}`,
|
result: `${result.isNumber ? -result.value : result.toString()}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Attribute damage',
|
name: 'Attribute damage',
|
||||||
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
|
||||||
result: `${-result}`,
|
result: `${result.isNumber ? -result.value : result.toString()}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
} from '/imports/api/parenting/parenting.js';
|
} from '/imports/api/parenting/parenting.js';
|
||||||
import {setDocToLastOrder} from '/imports/api/parenting/order.js';
|
import {setDocToLastOrder} from '/imports/api/parenting/order.js';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
|
||||||
|
|
||||||
export default function applyBuff({
|
export default function applyBuff({
|
||||||
prop,
|
prop,
|
||||||
@@ -58,10 +57,5 @@ function copyNodeListToTarget(propList, target, oldParent){
|
|||||||
collection: CreatureProperties,
|
collection: CreatureProperties,
|
||||||
doc: propList[0],
|
doc: propList[0],
|
||||||
});
|
});
|
||||||
|
CreatureProperties.batchInsert(propList);
|
||||||
CreatureProperties.batchInsert(propList, () => {
|
|
||||||
// This insert is racing the main recompute, recmpute again after it's
|
|
||||||
// certainly finished
|
|
||||||
recomputeCreatureByDoc(target);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,49 +15,79 @@ export default function applyDamage({
|
|||||||
...creature.variables,
|
...creature.variables,
|
||||||
...actionContext,
|
...actionContext,
|
||||||
};
|
};
|
||||||
|
// Add the target's variables to the scope
|
||||||
if (targets.length === 1){
|
if (targets.length === 1){
|
||||||
scope.target = targets[0].variables;
|
scope.target = targets[0].variables;
|
||||||
}
|
}
|
||||||
|
// Determine if the hit is critical
|
||||||
let criticalHit = !!(
|
let criticalHit = !!(
|
||||||
actionContext.criticalHit &&
|
actionContext.criticalHit &&
|
||||||
actionContext.criticalHit.value &&
|
actionContext.criticalHit.value &&
|
||||||
prop.damageType !== 'healing' // Can't critically heal
|
prop.damageType !== 'healing' // Can't critically heal
|
||||||
);
|
);
|
||||||
|
// Double the damage rolls if the hit is critical
|
||||||
let context = new CompilationContext({
|
let context = new CompilationContext({
|
||||||
doubleRolls: criticalHit,
|
doubleRolls: criticalHit,
|
||||||
});
|
});
|
||||||
try {
|
|
||||||
var {result, errors} = evaluateString(prop.amount, scope, 'reduce', context);
|
// Compute the roll the first time, logging any errors
|
||||||
if (typeof result !== 'number') {
|
var {result} = evaluateString({
|
||||||
log.content.push({
|
string: prop.amount,
|
||||||
error: errors.join(', '),
|
scope,
|
||||||
});
|
fn: 'reduce',
|
||||||
}
|
context
|
||||||
} catch (e){
|
});
|
||||||
|
|
||||||
|
// If the result is an error bail out now
|
||||||
|
if (result.constructor.name === 'ErrorNode'){
|
||||||
log.content.push({
|
log.content.push({
|
||||||
error: e.toString(),
|
name: 'Damage error',
|
||||||
|
error: result.toString(),
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memoise the damage suffix for the log
|
||||||
let suffix = (criticalHit ? ' critical ' : '') +
|
let suffix = (criticalHit ? ' critical ' : '') +
|
||||||
prop.damageType +
|
prop.damageType +
|
||||||
(prop.damageType !== 'healing' ? ' damage': '');
|
(prop.damageType !== 'healing' ? ' damage': '');
|
||||||
|
|
||||||
if (damageTargets && damageTargets.length) {
|
if (damageTargets && damageTargets.length) {
|
||||||
|
// Iterate through all the targets
|
||||||
damageTargets.forEach(target => {
|
damageTargets.forEach(target => {
|
||||||
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||||
|
|
||||||
|
// Reroll the damage if needed
|
||||||
if (prop.target === 'each'){
|
if (prop.target === 'each'){
|
||||||
result = evaluateString(prop.amount, scope, 'reduce');
|
({result, context} = evaluateString({
|
||||||
|
string: prop.amount,
|
||||||
|
scope,
|
||||||
|
fn: 'reduce'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
// If the result is an error or not a number bail out now
|
||||||
|
if (result.constructor.name === 'ErrorNode' || !result.isNumber){
|
||||||
|
log.content.push({
|
||||||
|
name: 'Damage error',
|
||||||
|
error: result.toString(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal the damage to the target
|
||||||
let damageDealt = dealDamage.call({
|
let damageDealt = dealDamage.call({
|
||||||
creatureId: target._id,
|
creatureId: target._id,
|
||||||
damageType: prop.damageType,
|
damageType: prop.damageType,
|
||||||
amount: result,
|
amount: result.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log the damage done
|
||||||
if (target._id === creature._id){
|
if (target._id === creature._id){
|
||||||
|
// Target is same as self, log damage as such
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name,
|
name,
|
||||||
result: damageDealt,
|
result: damageDealt,
|
||||||
details: suffix + 'to self',
|
details: suffix + ' to self',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
log.content.push({
|
log.content.push({
|
||||||
@@ -66,6 +96,7 @@ export default function applyDamage({
|
|||||||
result: damageDealt,
|
result: damageDealt,
|
||||||
details: suffix + `${target.name && ' to '}${target.name}`,
|
details: suffix + `${target.name && ' to '}${target.name}`,
|
||||||
});
|
});
|
||||||
|
// Log the damage received on that creature's log as well
|
||||||
insertCreatureLog.call({
|
insertCreatureLog.call({
|
||||||
log: {
|
log: {
|
||||||
content: [{
|
content: [{
|
||||||
@@ -80,9 +111,10 @@ export default function applyDamage({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// There are no targets, just log the result
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
||||||
result,
|
result: result.toString(),
|
||||||
details: suffix,
|
details: suffix,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import applySave from '/imports/api/creature/actions/applySave.js';
|
|||||||
function applyProperty(options){
|
function applyProperty(options){
|
||||||
let prop = options.prop;
|
let prop = options.prop;
|
||||||
if (prop.type === 'buff'){
|
if (prop.type === 'buff'){
|
||||||
// ignore only applied buffs
|
// ignore only applied buffs, don't apply them again
|
||||||
if (prop.applied === true){
|
if (prop.applied === true){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ function applyProperty(options){
|
|||||||
break;
|
break;
|
||||||
case 'buff':
|
case 'buff':
|
||||||
applyBuff(options);
|
applyBuff(options);
|
||||||
break;
|
return false;
|
||||||
case 'toggle':
|
case 'toggle':
|
||||||
return applyToggle(options);
|
return applyToggle(options);
|
||||||
case 'roll':
|
case 'roll':
|
||||||
|
|||||||
@@ -10,23 +10,17 @@ export default function applyRoll({
|
|||||||
...creature.variables,
|
...creature.variables,
|
||||||
...actionContext,
|
...actionContext,
|
||||||
};
|
};
|
||||||
try {
|
var {result} = evaluateString({
|
||||||
var {result, errors} = evaluateString(prop.roll, scope, 'reduce');
|
string: prop.roll,
|
||||||
actionContext[prop.variableName] = result;
|
scope,
|
||||||
log.content.push({
|
fn: 'reduce'
|
||||||
name: prop.name,
|
});
|
||||||
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
|
if (result.isNumber){
|
||||||
result,
|
actionContext[prop.variableName] = result.value;
|
||||||
});
|
|
||||||
if (errors.length) {
|
|
||||||
log.content.push({
|
|
||||||
error: errors.join(', '),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
|
||||||
error: e.toString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: prop.name,
|
||||||
|
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
|
||||||
|
result: result.toString(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,17 @@ export default function applySave({
|
|||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
// Calculate the DC
|
// Calculate the DC
|
||||||
var {result, errors} = evaluateString(prop.dc, scope, 'reduce');
|
var {result} = evaluateString({
|
||||||
let dc = result;
|
string: prop.dc,
|
||||||
|
scope,
|
||||||
|
fn: 'reduce'
|
||||||
|
});
|
||||||
|
let dc = result.value;
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.name,
|
name: prop.name,
|
||||||
resultPrefix: ' DC ',
|
resultPrefix: ' DC ',
|
||||||
result,
|
result: result.toString(),
|
||||||
});
|
});
|
||||||
if (errors.length) {
|
|
||||||
log.content.push({
|
|
||||||
error: errors.join(', '),
|
|
||||||
});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (prop.target === 'self'){
|
if (prop.target === 'self'){
|
||||||
let save = CreaturesProperties.findOne({
|
let save = CreaturesProperties.findOne({
|
||||||
'ancestors.id': creature._id,
|
'ancestors.id': creature._id,
|
||||||
|
|||||||
@@ -13,23 +13,22 @@ export default function applyToggle({
|
|||||||
if (Number.isFinite(+prop.condition)){
|
if (Number.isFinite(+prop.condition)){
|
||||||
return !!+prop.condition;
|
return !!+prop.condition;
|
||||||
}
|
}
|
||||||
try {
|
var {result} = evaluateString({
|
||||||
var {result, errors} = evaluateString(prop.condition, scope, 'reduce');
|
string: prop.condition,
|
||||||
if (typeof result !== 'number' && typeof result !== 'boolean') {
|
scope,
|
||||||
log.content.push({
|
fn: 'reduce'
|
||||||
error: errors.join(', '),
|
});
|
||||||
});
|
if (result.constructor.name === 'ErrorNode') {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: prop.name,
|
name: 'Toggle error',
|
||||||
resultPrefix: prop.condition + ' = ',
|
error: result.toString(),
|
||||||
result,
|
|
||||||
});
|
|
||||||
return !!result;
|
|
||||||
} catch (e){
|
|
||||||
log.content.push({
|
|
||||||
error: e.toString(),
|
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
log.content.push({
|
||||||
|
name: prop.name || 'Toggle',
|
||||||
|
resultPrefix: prop.condition + ' = ',
|
||||||
|
result: result.toString(),
|
||||||
|
});
|
||||||
|
return !!result.value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const castSpellWithSlot = new ValidatedMethod({
|
|||||||
action: spell,
|
action: spell,
|
||||||
context: {slotLevel},
|
context: {slotLevel},
|
||||||
creature,
|
creature,
|
||||||
target,
|
targets: [target],
|
||||||
method: this,
|
method: this,
|
||||||
});
|
});
|
||||||
// Note this only recomputes the top-level creature, not the nearest one
|
// Note this only recomputes the top-level creature, not the nearest one
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { recomputeCreatureByDoc } from '/imports/api/creature/computation/method
|
|||||||
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
import { nodesToTree } from '/imports/api/parenting/parenting.js';
|
||||||
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
|
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
|
||||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||||
|
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||||
|
|
||||||
const doAction = new ValidatedMethod({
|
const doAction = new ValidatedMethod({
|
||||||
name: 'creatureProperties.doAction',
|
name: 'creatureProperties.doAction',
|
||||||
@@ -46,9 +47,14 @@ const doAction = new ValidatedMethod({
|
|||||||
|
|
||||||
// The acting creature might have used ammo
|
// The acting creature might have used ammo
|
||||||
recomputeInventory(creature._id);
|
recomputeInventory(creature._id);
|
||||||
|
|
||||||
|
// The action might add properties which need to be activated
|
||||||
|
recomputeInactiveProperties(creature._id);
|
||||||
|
|
||||||
// recompute creatures
|
// recompute creatures
|
||||||
recomputeCreatureByDoc(creature);
|
recomputeCreatureByDoc(creature);
|
||||||
targets.forEach(target => {
|
targets.forEach(target => {
|
||||||
|
recomputeInactiveProperties(target._id);
|
||||||
recomputeCreatureByDoc(target);
|
recomputeCreatureByDoc(target);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default function spendResources({prop, log}){
|
|||||||
// Now that we have confirmed that there are no errors, do actual work
|
// Now that we have confirmed that there are no errors, do actual work
|
||||||
//Items
|
//Items
|
||||||
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
||||||
|
|
||||||
// Use uses
|
// Use uses
|
||||||
if (prop.usesResult){
|
if (prop.usesResult){
|
||||||
CreatureProperties.update(prop._id, {
|
CreatureProperties.update(prop._id, {
|
||||||
@@ -61,7 +61,7 @@ export default function spendResources({prop, log}){
|
|||||||
});
|
});
|
||||||
log.content.push({
|
log.content.push({
|
||||||
name: 'Uses left',
|
name: 'Uses left',
|
||||||
result: prop.usesResult - prop.usesUsed - 1,
|
result: prop.usesResult - (prop.usesUsed || 0) - 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,67 @@
|
|||||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
||||||
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
||||||
|
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||||
|
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||||
|
|
||||||
export default function evaluateString(string, scope, fn = 'compile', context){
|
//TODO replace constants with their parsed node
|
||||||
let errors = [];
|
|
||||||
|
export default function evaluateString({string, scope, fn = 'compile', context}){
|
||||||
|
if (!context){
|
||||||
|
context = new CompilationContext({});
|
||||||
|
}
|
||||||
if (!string){
|
if (!string){
|
||||||
errors.push('No string provided');
|
context.storeError('No string provided');
|
||||||
return {result: string, errors};
|
return {result: {value: string}, context};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scope) errors.push('No scope provided');
|
if (!scope) context.storeError('No scope provided');
|
||||||
|
|
||||||
// Parse the string using mathjs
|
// Parse the string using mathjs
|
||||||
let node;
|
let node;
|
||||||
try {
|
try {
|
||||||
node = parse(string);
|
node = parse(string);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errors.push(e);
|
context.storeError(e);
|
||||||
return {result: string, errors};
|
return {result: {value: string}, context};
|
||||||
}
|
|
||||||
if (!context){
|
|
||||||
context = new CompilationContext({});
|
|
||||||
}
|
}
|
||||||
|
node = replaceConstants({calc: node, context, scope});
|
||||||
let result = node[fn](scope, context);
|
let result = node[fn](scope, context);
|
||||||
if (result instanceof ConstantNode){
|
return {result, context};
|
||||||
return {result: result.value, errors: context.errors}
|
}
|
||||||
} else {
|
|
||||||
return {result: result.toString(), errors: context.errors};
|
// Replace constants in the calc with the right ParseNodes
|
||||||
}
|
function replaceConstants({calc, context, scope}){
|
||||||
|
let constFailed = [];
|
||||||
|
calc = calc.replaceNodes(node => {
|
||||||
|
if (!(node instanceof SymbolNode)) return;
|
||||||
|
let constant = scope[node.name];
|
||||||
|
// replace constants that aren't overridden by stats or disabled by a toggle
|
||||||
|
if (constant && constant.type === 'constant'){
|
||||||
|
// Fail if the constant has errors
|
||||||
|
if (constant.errors && constant.errors.length){
|
||||||
|
constFailed.push(node.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let parsedConstantNode;
|
||||||
|
try {
|
||||||
|
parsedConstantNode = parse(constant.calculation);
|
||||||
|
} catch(e){
|
||||||
|
constFailed.push(node.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!parsedConstantNode) constFailed.push(node.name);
|
||||||
|
return parsedConstantNode;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
constFailed.forEach(name => {
|
||||||
|
context.storeError({
|
||||||
|
type: 'error',
|
||||||
|
message: `${name} is a constant property with parsing errors`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let failed = !!constFailed.length;
|
||||||
|
if (failed){
|
||||||
|
calc = new ErrorNode({error: 'Failed to replace constants'});
|
||||||
|
}
|
||||||
|
return calc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
|
||||||
|
|
||||||
// Strings can have computations in bracers like so: {computation}
|
|
||||||
export default function evalutateStringWithEmbeddedCalculations(string, scope){
|
|
||||||
console.warn('evalutateStringWithEmbeddedCalculations should be replaced with ' +
|
|
||||||
'fetching the result from the compuations on the property doc');
|
|
||||||
if (!string) return string;
|
|
||||||
// Compute everything inside bracers
|
|
||||||
return string.replace(/\{([^{}]*)\}/g, function(match, p1){
|
|
||||||
let {result} = evaluateString(p1, scope);
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ export default class ComputationMemo {
|
|||||||
constructor(props, creature){
|
constructor(props, creature){
|
||||||
this.statsByVariableName = {};
|
this.statsByVariableName = {};
|
||||||
this.constantsByVariableName = {};
|
this.constantsByVariableName = {};
|
||||||
|
this.constantsById = {};
|
||||||
this.extraStatsByVariableName = {};
|
this.extraStatsByVariableName = {};
|
||||||
this.statsById = {};
|
this.statsById = {};
|
||||||
this.originalPropsById = {};
|
this.originalPropsById = {};
|
||||||
@@ -77,11 +78,7 @@ export default class ComputationMemo {
|
|||||||
}
|
}
|
||||||
addConstant(prop){
|
addConstant(prop){
|
||||||
prop = this.registerProperty(prop);
|
prop = this.registerProperty(prop);
|
||||||
if (
|
this.constantsById[prop._id] = prop;
|
||||||
!this.constantsByVariableName[prop.variableName]
|
|
||||||
){
|
|
||||||
this.constantsByVariableName[prop.variableName] = prop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
registerProperty(prop){
|
registerProperty(prop){
|
||||||
this.originalPropsById[prop._id] = cloneDeep(prop);
|
this.originalPropsById[prop._id] = cloneDeep(prop);
|
||||||
@@ -256,7 +253,6 @@ const propDetailsByType = {
|
|||||||
default(){
|
default(){
|
||||||
return {
|
return {
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
toggle(){
|
toggle(){
|
||||||
@@ -264,7 +260,6 @@ const propDetailsByType = {
|
|||||||
computed: false,
|
computed: false,
|
||||||
busyComputing: false,
|
busyComputing: false,
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
attribute(){
|
attribute(){
|
||||||
@@ -273,7 +268,6 @@ const propDetailsByType = {
|
|||||||
busyComputing: false,
|
busyComputing: false,
|
||||||
effects: [],
|
effects: [],
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
idsOfSameName: [],
|
idsOfSameName: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -284,7 +278,6 @@ const propDetailsByType = {
|
|||||||
effects: [],
|
effects: [],
|
||||||
proficiencies: [],
|
proficiencies: [],
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
idsOfSameName: [],
|
idsOfSameName: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -293,26 +286,22 @@ const propDetailsByType = {
|
|||||||
computed: false,
|
computed: false,
|
||||||
busyComputing: false,
|
busyComputing: false,
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
classLevel(){
|
classLevel(){
|
||||||
return {
|
return {
|
||||||
computed: true,
|
computed: true,
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
proficiency(){
|
proficiency(){
|
||||||
return {
|
return {
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
denormalizedStat(){
|
denormalizedStat(){
|
||||||
return {
|
return {
|
||||||
toggleAncestors: [],
|
toggleAncestors: [],
|
||||||
disabledByToggle: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import computeToggle from '/imports/api/creature/computation/engine/computeToggl
|
|||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
|
|
||||||
export default function applyToggles(prop, memo){
|
export default function applyToggles(prop, memo){
|
||||||
|
// If it used to be inactive delete those fields
|
||||||
|
if (prop.inactive) prop.inactive = undefined;
|
||||||
|
if (prop.deactivatedByAncestor) prop.deactivatedByAncestor = undefined;
|
||||||
|
if (prop.deactivatedByToggle) prop.deactivatedByToggle = undefined;
|
||||||
|
// Iterate through the toggle ancestors from oldest to nearest
|
||||||
prop.computationDetails.toggleAncestors.forEach(toggleId => {
|
prop.computationDetails.toggleAncestors.forEach(toggleId => {
|
||||||
let toggle = memo.togglesById[toggleId];
|
let toggle = memo.togglesById[toggleId];
|
||||||
computeToggle(toggle, memo);
|
computeToggle(toggle, memo);
|
||||||
@@ -10,8 +15,11 @@ export default function applyToggles(prop, memo){
|
|||||||
[toggle._id],
|
[toggle._id],
|
||||||
toggle.dependencies,
|
toggle.dependencies,
|
||||||
);
|
);
|
||||||
|
// Deactivate if the toggle is false
|
||||||
if (!toggle.toggleResult){
|
if (!toggle.toggleResult){
|
||||||
prop.computationDetails.disabledByToggle = true;
|
prop.inactive = true;
|
||||||
|
prop.deactivatedByAncestor = true;
|
||||||
|
prop.deactivatedByToggle = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function combineSkill(stat, aggregator, memo){
|
|||||||
let prof = stat.computationDetails.proficiencies[i];
|
let prof = stat.computationDetails.proficiencies[i];
|
||||||
applyToggles(prof, memo);
|
applyToggles(prof, memo);
|
||||||
if (
|
if (
|
||||||
!prof.computationDetails.disabledByToggle &&
|
!prof.deactivatedByToggle &&
|
||||||
prof.value > stat.proficiency
|
prof.value > stat.proficiency
|
||||||
){
|
){
|
||||||
stat.proficiency = prof.value;
|
stat.proficiency = prof.value;
|
||||||
@@ -112,13 +112,14 @@ function combineSkill(stat, aggregator, memo){
|
|||||||
let profBonus = profBonusStat && profBonusStat.value;
|
let profBonus = profBonusStat && profBonusStat.value;
|
||||||
|
|
||||||
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
|
||||||
let level = memo.statsByVariableName['level'].value;
|
let levelProp = memo.statsByVariableName['level'];
|
||||||
|
let level = levelProp.value;
|
||||||
profBonus = Math.ceil(level / 4) + 1;
|
profBonus = Math.ceil(level / 4) + 1;
|
||||||
if (level._id){
|
if (levelProp._id){
|
||||||
stat.dependencies = union(stat.dependencies, [level._id]);
|
stat.dependencies = union(stat.dependencies, [levelProp._id]);
|
||||||
}
|
}
|
||||||
if (level.dependencies){
|
if (levelProp.dependencies){
|
||||||
stat.dependencies = union(stat.dependencies, level.dependencies);
|
stat.dependencies = union(stat.dependencies, levelProp.dependencies);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stat.dependencies = union(
|
stat.dependencies = union(
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||||
|
|
||||||
|
export default function computeConstant(constant, memo){
|
||||||
|
// Apply any toggles
|
||||||
|
applyToggles(constant, memo);
|
||||||
|
if (constant.deactivatedByToggle) return;
|
||||||
|
if (
|
||||||
|
!memo.constantsByVariableName[constant.variableName]
|
||||||
|
){
|
||||||
|
memo.constantsByVariableName[constant.variableName] = constant
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
||||||
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
|
||||||
|
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
|
|
||||||
export default function computeEndStepProperty(prop, memo){
|
export default function computeEndStepProperty(prop, memo){
|
||||||
|
applyToggles(prop, memo);
|
||||||
|
|
||||||
switch (prop.type){
|
switch (prop.type){
|
||||||
case 'action':
|
case 'action':
|
||||||
case 'spell':
|
case 'spell':
|
||||||
@@ -75,26 +78,33 @@ function computeAction(prop, memo){
|
|||||||
});
|
});
|
||||||
// Items consumed
|
// Items consumed
|
||||||
prop.resources.itemsConsumed.forEach((itemConsumed, i) => {
|
prop.resources.itemsConsumed.forEach((itemConsumed, i) => {
|
||||||
let item = itemConsumed.itemId && memo.equipmentById[itemConsumed.itemId];
|
let item = itemConsumed.itemId ?
|
||||||
prop.resources.itemsConsumed[i].itemId = item && item._id;
|
memo.equipmentById[itemConsumed.itemId] :
|
||||||
let available = item && item.quantity || 0;
|
undefined;
|
||||||
|
let available = item ? item.quantity : 0;
|
||||||
prop.resources.itemsConsumed[i].available = available;
|
prop.resources.itemsConsumed[i].available = available;
|
||||||
let name = item && item.name;
|
|
||||||
if (item && item.quantity !== 1 && item.plural){
|
|
||||||
name = item.plural;
|
|
||||||
}
|
|
||||||
prop.resources.itemsConsumed[i].itemName = name;
|
|
||||||
prop.resources.itemsConsumed[i].itemIcon = item && item.icon;
|
|
||||||
prop.resources.itemsConsumed[i].itemColor = item && item.color;
|
|
||||||
if (!item || available < itemConsumed.quantity){
|
if (!item || available < itemConsumed.quantity){
|
||||||
prop.insufficientResources = true;
|
prop.insufficientResources = true;
|
||||||
}
|
}
|
||||||
if (item){
|
if (item){
|
||||||
|
prop.resources.itemsConsumed[i].itemId = item._id;
|
||||||
|
let name = item.name;
|
||||||
|
if (item.quantity !== 1 && item.plural){
|
||||||
|
name = item.plural;
|
||||||
|
}
|
||||||
|
if (name) prop.resources.itemsConsumed[i].itemName = name;
|
||||||
|
if (item.icon) prop.resources.itemsConsumed[i].itemIcon = item.icon;
|
||||||
|
if (item.color) prop.resources.itemsConsumed[i].itemColor = item.color;
|
||||||
prop.dependencies = union(
|
prop.dependencies = union(
|
||||||
prop.dependencies,
|
prop.dependencies,
|
||||||
[item._id],
|
[item._id],
|
||||||
item.dependencies
|
item.dependencies
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
delete prop.resources.itemsConsumed[i].itemId;
|
||||||
|
delete prop.resources.itemsConsumed[i].itemName;
|
||||||
|
delete prop.resources.itemsConsumed[i].itemIcon;
|
||||||
|
delete prop.resources.itemsConsumed[i].itemColor;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { forOwn, has, union } from 'lodash';
|
import { forOwn, has, union } from 'lodash';
|
||||||
|
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||||
|
|
||||||
export default function computeLevels(memo){
|
export default function computeLevels(memo){
|
||||||
computeClassLevels(memo);
|
computeClassLevels(memo);
|
||||||
@@ -7,11 +8,13 @@ export default function computeLevels(memo){
|
|||||||
|
|
||||||
function computeClassLevels(memo){
|
function computeClassLevels(memo){
|
||||||
forOwn(memo.classLevelsById, classLevel => {
|
forOwn(memo.classLevelsById, classLevel => {
|
||||||
|
applyToggles(classLevel, memo);
|
||||||
// class levels are mutually dependent
|
// class levels are mutually dependent
|
||||||
classLevel.dependencies = union(
|
classLevel.dependencies = union(
|
||||||
classLevel.dependencies,
|
classLevel.dependencies,
|
||||||
Object.keys(memo.classLevelsById)
|
Object.keys(memo.classLevelsById)
|
||||||
);
|
);
|
||||||
|
if (classLevel.deactivatedByToggle) return;
|
||||||
let name = classLevel.variableName;
|
let name = classLevel.variableName;
|
||||||
let stat = memo.statsByVariableName[name];
|
let stat = memo.statsByVariableName[name];
|
||||||
if (!stat){
|
if (!stat){
|
||||||
@@ -29,7 +32,7 @@ function computeClassLevels(memo){
|
|||||||
|
|
||||||
function computeTotalLevel(memo){
|
function computeTotalLevel(memo){
|
||||||
let currentLevel = memo.statsByVariableName['level'];
|
let currentLevel = memo.statsByVariableName['level'];
|
||||||
if (!currentLevel){
|
if (!currentLevel || currentLevel.deactivatedByToggle){
|
||||||
currentLevel = {
|
currentLevel = {
|
||||||
value: 0,
|
value: 0,
|
||||||
dependencies: [],
|
dependencies: [],
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import computeEffect from '/imports/api/creature/computation/engine/computeEffec
|
|||||||
import computeToggle from '/imports/api/creature/computation/engine/computeToggle.js';
|
import computeToggle from '/imports/api/creature/computation/engine/computeToggle.js';
|
||||||
import computeEndStepProperty from '/imports/api/creature/computation/engine/computeEndStepProperty.js';
|
import computeEndStepProperty from '/imports/api/creature/computation/engine/computeEndStepProperty.js';
|
||||||
import computeInlineCalculations from '/imports/api/creature/computation/engine/computeInlineCalculations.js';
|
import computeInlineCalculations from '/imports/api/creature/computation/engine/computeInlineCalculations.js';
|
||||||
|
import computeConstant from '/imports/api/creature/computation/engine/computeConstant.js';
|
||||||
|
|
||||||
export default function computeMemo(memo){
|
export default function computeMemo(memo){
|
||||||
|
// Compute all constants that could be used
|
||||||
|
forOwn(memo.constantsById, constant => {
|
||||||
|
computeConstant (constant, memo);
|
||||||
|
});
|
||||||
// Compute level
|
// Compute level
|
||||||
computeLevels(memo);
|
computeLevels(memo);
|
||||||
// Compute all stats, even if they are overriden
|
// Compute all stats, even if they are overriden
|
||||||
|
|||||||
@@ -22,28 +22,25 @@ export default function computeStat(stat, memo){
|
|||||||
// Apply any toggles
|
// Apply any toggles
|
||||||
applyToggles(stat, memo);
|
applyToggles(stat, memo);
|
||||||
|
|
||||||
if (!stat.computationDetails.disabledByToggle){
|
// Compute and aggregate all the effects
|
||||||
// Compute and aggregate all the effects
|
let aggregator = new EffectAggregator(stat, memo)
|
||||||
let aggregator = new EffectAggregator(stat, memo)
|
each(stat.computationDetails.effects, (effect) => {
|
||||||
each(stat.computationDetails.effects, (effect) => {
|
computeEffect(effect, memo);
|
||||||
computeEffect(effect, memo);
|
if (effect.deactivatedByToggle) return;
|
||||||
if (effect._id){
|
if (effect._id){
|
||||||
stat.dependencies = union(
|
|
||||||
stat.dependencies,
|
|
||||||
[effect._id]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
stat.dependencies = union(
|
stat.dependencies = union(
|
||||||
stat.dependencies,
|
stat.dependencies,
|
||||||
effect.dependencies
|
[effect._id]
|
||||||
)
|
);
|
||||||
if (!effect.computationDetails.disabledByToggle){
|
}
|
||||||
aggregator.addEffect(effect);
|
stat.dependencies = union(
|
||||||
}
|
stat.dependencies,
|
||||||
});
|
effect.dependencies
|
||||||
// Conglomerate all the effects to compute the final stat values
|
)
|
||||||
combineStat(stat, aggregator, memo);
|
aggregator.addEffect(effect);
|
||||||
}
|
});
|
||||||
|
// Conglomerate all the effects to compute the final stat values
|
||||||
|
combineStat(stat, aggregator, memo);
|
||||||
// Mark the attribute as computed
|
// Mark the attribute as computed
|
||||||
stat.computationDetails.computed = true;
|
stat.computationDetails.computed = true;
|
||||||
stat.computationDetails.busyComputing = false;
|
stat.computationDetails.busyComputing = false;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
|
||||||
|
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
|
||||||
import { union } from 'lodash';
|
import { union } from 'lodash';
|
||||||
|
|
||||||
export default function computeToggle(toggle, memo){
|
export default function computeToggle(toggle, memo){
|
||||||
@@ -16,6 +17,9 @@ export default function computeToggle(toggle, memo){
|
|||||||
// Before doing any work, mark this toggle as busy
|
// Before doing any work, mark this toggle as busy
|
||||||
toggle.computationDetails.busyComputing = true;
|
toggle.computationDetails.busyComputing = true;
|
||||||
|
|
||||||
|
// Apply any parent toggles
|
||||||
|
applyToggles(toggle, memo);
|
||||||
|
|
||||||
// Do work
|
// Do work
|
||||||
delete toggle.errors;
|
delete toggle.errors;
|
||||||
if (toggle.enabled){
|
if (toggle.enabled){
|
||||||
@@ -41,6 +45,11 @@ export default function computeToggle(toggle, memo){
|
|||||||
toggle.errors = context.errors;
|
toggle.errors = context.errors;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!toggle.toggleResult){
|
||||||
|
toggle.inactive = true;
|
||||||
|
toggle.deactivatedBySelf = true;
|
||||||
|
toggle.deactivatedByToggle = true;
|
||||||
|
}
|
||||||
toggle.computationDetails.computed = true;
|
toggle.computationDetails.computed = true;
|
||||||
toggle.computationDetails.busyComputing = false;
|
toggle.computationDetails.busyComputing = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ function replaceConstants({calc, memo, prop, dependencies, context}){
|
|||||||
} else if (node.name === '#constant'){
|
} else if (node.name === '#constant'){
|
||||||
constant = findAncestorByType({type: 'constant', prop, memo});
|
constant = findAncestorByType({type: 'constant', prop, memo});
|
||||||
}
|
}
|
||||||
// replace constants that aren't overridden by stats
|
// replace constants that aren't overridden by stats or disabled by a toggle
|
||||||
if (constant && !stat){
|
if (constant && !constant.deactivatedByToggle && !stat){
|
||||||
dependencies = union(dependencies, [
|
dependencies = union(dependencies, [
|
||||||
constant._id,
|
constant._id,
|
||||||
...constant.dependencies
|
...constant.dependencies
|
||||||
|
|||||||
@@ -1,15 +1,6 @@
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
|
||||||
export default function getComputationProperties(creatureId){
|
export default function getComputationProperties(creatureId){
|
||||||
// find ids of all toggles that have conditions, even if they are inactive
|
|
||||||
let toggleIds = CreatureProperties.find({
|
|
||||||
'ancestors.id': creatureId,
|
|
||||||
type: 'toggle',
|
|
||||||
removed: {$ne: true},
|
|
||||||
condition: { $exists: true },
|
|
||||||
}, {
|
|
||||||
fields: {_id: 1},
|
|
||||||
}).map(t => t._id);
|
|
||||||
// Find all the relevant properties
|
// Find all the relevant properties
|
||||||
return CreatureProperties.find({
|
return CreatureProperties.find({
|
||||||
'ancestors.id': creatureId,
|
'ancestors.id': creatureId,
|
||||||
@@ -17,17 +8,15 @@ export default function getComputationProperties(creatureId){
|
|||||||
$or: [
|
$or: [
|
||||||
// All active properties
|
// All active properties
|
||||||
{inactive: {$ne: true}},
|
{inactive: {$ne: true}},
|
||||||
// All active and inactive toggles with conditions
|
// Unless they were deactivated because of a toggle
|
||||||
// Same as {$in: toggleIds}, but should be slightly faster
|
{deactivatedByToggle: true},
|
||||||
{type: 'toggle', condition: { $exists: true }},
|
|
||||||
// All decendents of the above toggles
|
|
||||||
{'ancestors.id': {$in: toggleIds}},
|
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
// Filter out fields never used by calculations
|
// Filter out fields never used by calculations
|
||||||
fields: {
|
fields: {
|
||||||
icon: 0,
|
icon: 0,
|
||||||
},
|
},
|
||||||
|
// Obey tree order
|
||||||
sort: {
|
sort: {
|
||||||
order: 1,
|
order: 1,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ export default function writeAlteredProperties(memo){
|
|||||||
// Loop through all properties on the memo
|
// Loop through all properties on the memo
|
||||||
forOwn(memo.propsById, changed => {
|
forOwn(memo.propsById, changed => {
|
||||||
let schema = propertySchemasIndex[changed.type];
|
let schema = propertySchemasIndex[changed.type];
|
||||||
if (!schema) return;
|
if (!schema){
|
||||||
|
console.warn('No schema for ' + changed.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
let extraIds = changed.computationDetails.idsOfSameName;
|
let extraIds = changed.computationDetails.idsOfSameName;
|
||||||
let ids;
|
let ids;
|
||||||
if (extraIds && extraIds.length){
|
if (extraIds && extraIds.length){
|
||||||
@@ -19,7 +22,14 @@ export default function writeAlteredProperties(memo){
|
|||||||
ids.forEach(id => {
|
ids.forEach(id => {
|
||||||
let op = undefined;
|
let op = undefined;
|
||||||
let original = memo.originalPropsById[id];
|
let original = memo.originalPropsById[id];
|
||||||
let keys = ['dependencies', ...schema.objectKeys()];
|
let keys = [
|
||||||
|
'dependencies',
|
||||||
|
'inactive',
|
||||||
|
'deactivatedBySelf',
|
||||||
|
'deactivatedByAncestor',
|
||||||
|
'deactivatedByToggle',
|
||||||
|
...schema.objectKeys(),
|
||||||
|
];
|
||||||
op = addChangedKeysToOp(op, keys, original, changed);
|
op = addChangedKeysToOp(op, keys, original, changed);
|
||||||
if (op){
|
if (op){
|
||||||
bulkWriteOperations.push(op);
|
bulkWriteOperations.push(op);
|
||||||
|
|||||||
@@ -4,36 +4,46 @@ import VERSION from '/imports/constants/VERSION.js';
|
|||||||
|
|
||||||
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
|
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
|
||||||
const fields = [
|
const fields = [
|
||||||
'name',
|
|
||||||
'attributeType',
|
|
||||||
'baseValue',
|
|
||||||
'spellSlotLevelValue',
|
|
||||||
'damage',
|
|
||||||
'decimal',
|
|
||||||
'reset',
|
|
||||||
'resetMultiplier',
|
|
||||||
'value',
|
|
||||||
'currentValue',
|
|
||||||
'modifier',
|
|
||||||
'ability',
|
'ability',
|
||||||
'skillType',
|
|
||||||
'baseProficiency',
|
|
||||||
'abilityMod',
|
'abilityMod',
|
||||||
'advantage',
|
'advantage',
|
||||||
'passiveBonus',
|
'attributeType',
|
||||||
'proficiency',
|
'baseProficiency',
|
||||||
|
'baseValue',
|
||||||
|
'calculation',
|
||||||
'conditionalBenefits',
|
'conditionalBenefits',
|
||||||
'rollBonuses',
|
'currentValue',
|
||||||
|
'damage',
|
||||||
|
'decimal',
|
||||||
'fail',
|
'fail',
|
||||||
'level',
|
'level',
|
||||||
|
'modifier',
|
||||||
|
'name',
|
||||||
|
'passiveBonus',
|
||||||
|
'proficiency',
|
||||||
|
'reset',
|
||||||
|
'resetMultiplier',
|
||||||
|
'rollBonuses',
|
||||||
|
'skillType',
|
||||||
|
'spellSlotLevelValue',
|
||||||
|
'type',
|
||||||
|
'value',
|
||||||
];
|
];
|
||||||
|
|
||||||
if (fullRecompute){
|
if (fullRecompute){
|
||||||
memo.creatureVariables = {};
|
memo.creatureVariables = {};
|
||||||
forOwn(memo.statsByVariableName, (stat, variableName) => {
|
forOwn(memo.statsByVariableName, (stat, variableName) => {
|
||||||
|
// Don't save context variables
|
||||||
|
if (variableName[0] === '#') return;
|
||||||
let condensedStat = pick(stat, fields);
|
let condensedStat = pick(stat, fields);
|
||||||
memo.creatureVariables[variableName] = condensedStat;
|
memo.creatureVariables[variableName] = condensedStat;
|
||||||
});
|
});
|
||||||
|
forOwn(memo.constantsByVariableName, (stat, variableName) => {
|
||||||
|
let condensedStat = pick(stat, fields);
|
||||||
|
if (!memo.creatureVariables[variableName]){
|
||||||
|
memo.creatureVariables[variableName] = condensedStat;
|
||||||
|
}
|
||||||
|
});
|
||||||
Creatures.update(creatureId, {$set: {
|
Creatures.update(creatureId, {$set: {
|
||||||
variables: memo.creatureVariables,
|
variables: memo.creatureVariables,
|
||||||
computeVersion: VERSION,
|
computeVersion: VERSION,
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ export function recomputeCreatureByDoc(creature){
|
|||||||
writeCreatureVariables(computationMemo, creatureId);
|
writeCreatureVariables(computationMemo, creatureId);
|
||||||
recomputeDamageMultipliersById(creatureId);
|
recomputeDamageMultipliersById(creatureId);
|
||||||
recomputeSlotFullness(creatureId);
|
recomputeSlotFullness(creatureId);
|
||||||
recomputeInactiveProperties(creatureId);
|
|
||||||
return computationMemo;
|
return computationMemo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ let CreaturePropertySchema = new SimpleSchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
index: 1,
|
index: 1,
|
||||||
},
|
},
|
||||||
|
// Denormalised flag if this property was made inactive because of a toggle
|
||||||
|
// calculation. Either an ancestor toggle calculation or its own.
|
||||||
|
deactivatedByToggle: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
// Denormalised list of all properties or creatures this property depends on
|
// Denormalised list of all properties or creatures this property depends on
|
||||||
dependencies: {
|
dependencies: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const damagePropertiesByName = new ValidatedMethod({
|
|||||||
damagePropertyWork({property, operation, value});
|
damagePropertyWork({property, operation, value});
|
||||||
lastProperty = property;
|
lastProperty = property;
|
||||||
});
|
});
|
||||||
recomputePropertyDependencies(lastProperty);
|
if (lastProperty) recomputePropertyDependencies(lastProperty);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ const damageProperty = new ValidatedMethod({
|
|||||||
run({_id, operation, value}) {
|
run({_id, operation, value}) {
|
||||||
// Check permissions
|
// Check permissions
|
||||||
let property = CreatureProperties.findOne(_id);
|
let property = CreatureProperties.findOne(_id);
|
||||||
|
if (!property) throw new Meteor.Error(
|
||||||
|
'Damage property failed', 'Property doesn\'t exist'
|
||||||
|
);
|
||||||
let rootCreature = getRootCreatureAncestor(property);
|
let rootCreature = getRootCreatureAncestor(property);
|
||||||
assertEditPermission(rootCreature, this.userId);
|
assertEditPermission(rootCreature, this.userId);
|
||||||
// Check if property can take damage
|
// Check if property can take damage
|
||||||
@@ -34,9 +37,10 @@ const damageProperty = new ValidatedMethod({
|
|||||||
`Property of type "${property.type}" can't be damaged`
|
`Property of type "${property.type}" can't be damaged`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
damagePropertyWork({property, operation, value});
|
let result = damagePropertyWork({property, operation, value});
|
||||||
// Dependencies can't be changed through damage, only recompute deps
|
// Dependencies can't be changed through damage, only recompute deps
|
||||||
recomputePropertyDependencies(property);
|
recomputePropertyDependencies(property);
|
||||||
|
return result;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js
|
|||||||
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||||
|
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||||
import INVENTORY_TAGS from '/imports/constants/INVENTORY_TAGS.js';
|
import INVENTORY_TAGS from '/imports/constants/INVENTORY_TAGS.js';
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ const equipItem = new ValidatedMethod({
|
|||||||
skipRecompute: true,
|
skipRecompute: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
recomputeInactiveProperties(creature._id);
|
||||||
recomputeInventory(creature._id);
|
recomputeInventory(creature._id);
|
||||||
recomputeCreatureByDoc(creature);
|
recomputeCreatureByDoc(creature);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||||
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
|
||||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
|
||||||
|
|
||||||
@@ -59,7 +59,8 @@ const updateCreatureProperty = new ValidatedMethod({
|
|||||||
recomputeInventory(rootCreature._id);
|
recomputeInventory(rootCreature._id);
|
||||||
}
|
}
|
||||||
// Updating a property is likely to change dependencies, do a full recompute
|
// Updating a property is likely to change dependencies, do a full recompute
|
||||||
recomputeCreatureByDoc(rootCreature);
|
// denormalised stats might change, so fetch the creature again
|
||||||
|
recomputeCreatureById(rootCreature._id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export default function recomputeInactiveProperties(ancestorId){
|
|||||||
{disabled: true}, // Everything can be disabled
|
{disabled: true}, // Everything can be disabled
|
||||||
{type: 'buff', applied: false}, // Buffs can be applied
|
{type: 'buff', applied: false}, // Buffs can be applied
|
||||||
{type: 'item', equipped: {$ne: true}},
|
{type: 'item', equipped: {$ne: true}},
|
||||||
{type: 'toggle', toggleResult: false},
|
|
||||||
{type: 'spell', prepared: {$ne: true}, alwaysPrepared: {$ne: true}},
|
{type: 'spell', prepared: {$ne: true}, alwaysPrepared: {$ne: true}},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -56,14 +55,18 @@ export default function recomputeInactiveProperties(ancestorId){
|
|||||||
CreatureProperties.update({
|
CreatureProperties.update({
|
||||||
'ancestors.id': {$eq: ancestorId, $nin: disabledIds},
|
'ancestors.id': {$eq: ancestorId, $nin: disabledIds},
|
||||||
'_id': {$nin: disabledIds},
|
'_id': {$nin: disabledIds},
|
||||||
|
// if it was a toggle responsible, we leave it alone
|
||||||
|
deactivatedByToggle: {$ne: true},
|
||||||
$or: [
|
$or: [
|
||||||
{inactive: true},
|
{inactive: true},
|
||||||
{deactivatedByAncestor: true},
|
{deactivatedByAncestor: true},
|
||||||
|
{deactivatedBySelf: true}
|
||||||
],
|
],
|
||||||
}, {
|
}, {
|
||||||
$unset: {
|
$unset: {
|
||||||
inactive: 1,
|
inactive: 1,
|
||||||
deactivatedByAncestor: 1,
|
deactivatedByAncestor: 1,
|
||||||
|
deactivatedBySelf: 1,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
multi: true,
|
multi: true,
|
||||||
|
|||||||
@@ -84,13 +84,13 @@ function getInventoryData(tree, containersToWrite){
|
|||||||
for (let key in data){
|
for (let key in data){
|
||||||
data[key] += childData[key];
|
data[key] += childData[key];
|
||||||
}
|
}
|
||||||
|
if (node.contentsWeightless){
|
||||||
|
data.weightCarried = node.weight;
|
||||||
|
}
|
||||||
if (node.carried === false){
|
if (node.carried === false){
|
||||||
data.weightCarried = 0;
|
data.weightCarried = 0;
|
||||||
data.valueCarried = 0;
|
data.valueCarried = 0;
|
||||||
}
|
}
|
||||||
if (node.contentsWeightless){
|
|
||||||
data.weightCarried = node.weight;
|
|
||||||
}
|
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,14 @@ const removeCreature = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
run({charId}) {
|
run({charId}) {
|
||||||
assertOwnership(charId, this.userId)
|
assertOwnership(charId, this.userId)
|
||||||
Creatures.remove(charId);
|
this.unblock();
|
||||||
this.unblock();
|
removeCreatureWork(charId)
|
||||||
removeRelatedDocuments(charId);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export function removeCreatureWork(creatureId){
|
||||||
|
Creatures.remove(creatureId);
|
||||||
|
removeRelatedDocuments(creatureId);
|
||||||
|
}
|
||||||
|
|
||||||
export default removeCreature;
|
export default removeCreature;
|
||||||
|
|||||||
@@ -118,10 +118,14 @@ const removeLibrary = new ValidatedMethod({
|
|||||||
run({_id}){
|
run({_id}){
|
||||||
let library = Libraries.findOne(_id);
|
let library = Libraries.findOne(_id);
|
||||||
assertOwnership(library, this.userId);
|
assertOwnership(library, this.userId);
|
||||||
Libraries.remove(_id);
|
this.unblock();
|
||||||
this.unblock();
|
removeLibaryWork(_id)
|
||||||
LibraryNodes.remove({'ancestors.id': _id});
|
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
export function removeLibaryWork(libraryId){
|
||||||
|
Libraries.remove(libraryId);
|
||||||
|
LibraryNodes.remove({'ancestors.id': libraryId});
|
||||||
|
}
|
||||||
|
|
||||||
export { LibrarySchema, insertLibrary, setLibraryDefault, updateLibraryName, removeLibrary };
|
export { LibrarySchema, insertLibrary, setLibraryDefault, updateLibraryName, removeLibrary };
|
||||||
|
|||||||
@@ -4,21 +4,22 @@ import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustment
|
|||||||
import { ComputedOnlyAttackSchema } from '/imports/api/properties/Attacks.js';
|
import { ComputedOnlyAttackSchema } from '/imports/api/properties/Attacks.js';
|
||||||
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||||
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
||||||
// import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||||
|
import { ConstantSchema } from '/imports/api/properties/Constants.js';
|
||||||
import { ComputedOnlyContainerSchema } from '/imports/api/properties/Containers.js';
|
import { ComputedOnlyContainerSchema } from '/imports/api/properties/Containers.js';
|
||||||
import { ComputedOnlyDamageSchema } from '/imports/api/properties/Damages.js';
|
import { ComputedOnlyDamageSchema } from '/imports/api/properties/Damages.js';
|
||||||
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
||||||
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js';
|
import { ComputedOnlyEffectSchema } from '/imports/api/properties/Effects.js';
|
||||||
import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
||||||
// import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
// import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||||
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
import { ComputedOnlySavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||||
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
import { ComputedOnlySkillSchema } from '/imports/api/properties/Skills.js';
|
||||||
import { ComputedOnlySlotSchema } from '/imports/api/properties/Slots.js';
|
import { ComputedOnlySlotSchema } from '/imports/api/properties/Slots.js';
|
||||||
// import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
|
import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
|
||||||
import { ComputedOnlySpellSchema } from '/imports/api/properties/Spells.js';
|
import { ComputedOnlySpellSchema } from '/imports/api/properties/Spells.js';
|
||||||
import { ComputedOnlySpellListSchema } from '/imports/api/properties/SpellLists.js';
|
import { ComputedOnlySpellListSchema } from '/imports/api/properties/SpellLists.js';
|
||||||
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
|
import { ComputedOnlyToggleSchema } from '/imports/api/properties/Toggles.js';
|
||||||
@@ -29,23 +30,25 @@ const propertySchemasIndex = {
|
|||||||
attack: ComputedOnlyAttackSchema,
|
attack: ComputedOnlyAttackSchema,
|
||||||
attribute: ComputedOnlyAttributeSchema,
|
attribute: ComputedOnlyAttributeSchema,
|
||||||
buff: ComputedOnlyBuffSchema,
|
buff: ComputedOnlyBuffSchema,
|
||||||
// classLevel: ClassLevelSchema,
|
classLevel: ClassLevelSchema,
|
||||||
|
constant: ConstantSchema,
|
||||||
|
container: ComputedOnlyContainerSchema,
|
||||||
damage: ComputedOnlyDamageSchema,
|
damage: ComputedOnlyDamageSchema,
|
||||||
damageMultiplier: DamageMultiplierSchema,
|
damageMultiplier: DamageMultiplierSchema,
|
||||||
effect: ComputedOnlyEffectSchema,
|
effect: ComputedOnlyEffectSchema,
|
||||||
feature: ComputedOnlyFeatureSchema,
|
feature: ComputedOnlyFeatureSchema,
|
||||||
// folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
|
item: ComputedOnlyItemSchema,
|
||||||
note: ComputedOnlyNoteSchema,
|
note: ComputedOnlyNoteSchema,
|
||||||
// proficiency: ProficiencySchema,
|
proficiency: ProficiencySchema,
|
||||||
propertySlot: ComputedOnlySlotSchema,
|
propertySlot: ComputedOnlySlotSchema,
|
||||||
roll: ComputedOnlyRollSchema,
|
roll: ComputedOnlyRollSchema,
|
||||||
savingThrow: ComputedOnlySavingThrowSchema,
|
savingThrow: ComputedOnlySavingThrowSchema,
|
||||||
skill: ComputedOnlySkillSchema,
|
skill: ComputedOnlySkillSchema,
|
||||||
|
slotFiller: SlotFillerSchema,
|
||||||
spellList: ComputedOnlySpellListSchema,
|
spellList: ComputedOnlySpellListSchema,
|
||||||
spell: ComputedOnlySpellSchema,
|
spell: ComputedOnlySpellSchema,
|
||||||
toggle: ComputedOnlyToggleSchema,
|
toggle: ComputedOnlyToggleSchema,
|
||||||
container: ComputedOnlyContainerSchema,
|
|
||||||
item: ComputedOnlyItemSchema,
|
|
||||||
any: new SimpleSchema({}),
|
any: new SimpleSchema({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import '/imports/api/users/deleteMyAccount.js';
|
||||||
|
|
||||||
const userSchema = new SimpleSchema({
|
const userSchema = new SimpleSchema({
|
||||||
username: {
|
username: {
|
||||||
|
|||||||
61
app/imports/api/users/deleteMyAccount.js
Normal file
61
app/imports/api/users/deleteMyAccount.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import Libraries, {removeLibaryWork} from '/imports/api/library/Libraries.js';
|
||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import {removeCreatureWork} from '/imports/api/creature/removeCreature.js';
|
||||||
|
|
||||||
|
Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||||
|
name: 'users.deleteMyAccount',
|
||||||
|
validate: null,
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 1,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run(){
|
||||||
|
let userId = Meteor.userId();
|
||||||
|
if (!userId) throw new Meteor.Error('No user',
|
||||||
|
'You must be logged into to delete your account');
|
||||||
|
|
||||||
|
// Delete all creatures
|
||||||
|
let creatures = Creatures.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||||
|
creatures.forEach(creature => removeCreatureWork(creature._id));
|
||||||
|
|
||||||
|
// Remove permissions from all creatures
|
||||||
|
Creatures.update({
|
||||||
|
$or: [
|
||||||
|
{writers: userId},
|
||||||
|
{readers: userId},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
$pull: {
|
||||||
|
writers: userId,
|
||||||
|
readers: userId
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
multi: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete all libraries
|
||||||
|
let libraries = Libraries.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||||
|
libraries.forEach(library => removeLibaryWork(library._id));
|
||||||
|
|
||||||
|
// Remove permissions from all creatures
|
||||||
|
Libraries.update({
|
||||||
|
$or: [
|
||||||
|
{writers: userId},
|
||||||
|
{readers: userId},
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
$pull: {
|
||||||
|
writers: userId,
|
||||||
|
readers: userId
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
multi: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete the account
|
||||||
|
Meteor.users.remove(userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
|
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
|
||||||
|
import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
|
||||||
|
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||||
|
|
||||||
export default class IndexNode extends ParseNode {
|
export default class IndexNode extends ParseNode {
|
||||||
constructor({array, index}) {
|
constructor({array, index}) {
|
||||||
@@ -8,16 +10,42 @@ export default class IndexNode extends ParseNode {
|
|||||||
}
|
}
|
||||||
resolve(fn, scope, context){
|
resolve(fn, scope, context){
|
||||||
let index = this.index[fn](scope, context);
|
let index = this.index[fn](scope, context);
|
||||||
if (index.isInteger){
|
let array = this.array[fn](scope, context);
|
||||||
let selection = this.array.values[index.value - 1];
|
|
||||||
|
if (index.isInteger && array instanceof ArrayNode){
|
||||||
|
if (index.value < 1 || index.value > array.values.length){
|
||||||
|
if (context){
|
||||||
|
context.storeError({
|
||||||
|
type: 'warning',
|
||||||
|
message: `Index of ${index.value} is out of range for an array` +
|
||||||
|
` of length ${array.values.length}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let selection = array.values[index.value - 1];
|
||||||
if (selection){
|
if (selection){
|
||||||
let result = selection[fn](scope, context);
|
let result = selection[fn](scope, context);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
} else if (fn === 'reduce'){
|
||||||
|
if (!(array instanceof ArrayNode)){
|
||||||
|
return new ErrorNode({
|
||||||
|
node: this,
|
||||||
|
error: 'Can not get the index of a non-array node: ' +
|
||||||
|
this.array.toString() + ' = ' + array.toString(),
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
} else if (!index.isInteger){
|
||||||
|
return new ErrorNode({
|
||||||
|
node: this,
|
||||||
|
error: array.toString() + ' is not an integer index of the array',
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new IndexNode({
|
return new IndexNode({
|
||||||
index,
|
index,
|
||||||
array: this.array[fn](scope, context),
|
array,
|
||||||
previousNodes: [this],
|
previousNodes: [this],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default class ParenthesisNode extends ParseNode {
|
|||||||
resolve(fn, scope, context){
|
resolve(fn, scope, context){
|
||||||
let content = this.content[fn](scope, context);
|
let content = this.content[fn](scope, context);
|
||||||
if (
|
if (
|
||||||
|
fn === 'reduce' ||
|
||||||
content.constructor.name === 'ConstantNode' ||
|
content.constructor.name === 'ConstantNode' ||
|
||||||
content.constructor.name === 'ErrorNode'
|
content.constructor.name === 'ErrorNode'
|
||||||
){
|
){
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ import '/imports/server/publications/users.js';
|
|||||||
import '/imports/server/publications/icons.js';
|
import '/imports/server/publications/icons.js';
|
||||||
import '/imports/server/publications/tabletops.js';
|
import '/imports/server/publications/tabletops.js';
|
||||||
import '/imports/server/publications/slotFillers.js'
|
import '/imports/server/publications/slotFillers.js'
|
||||||
|
import '/imports/server/publications/ownedDocuments.js'
|
||||||
|
|||||||
@@ -25,31 +25,48 @@ Meteor.publish('libraries', function(){
|
|||||||
{owner: this.userId},
|
{owner: this.userId},
|
||||||
{writers: this.userId},
|
{writers: this.userId},
|
||||||
{readers: this.userId},
|
{readers: this.userId},
|
||||||
{_id: {$in: subs}},
|
{ _id: {$in: subs}, public: true },
|
||||||
]
|
]
|
||||||
|
}, {
|
||||||
|
sort: {name: 1}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish('library', function(libraryId){
|
||||||
|
if (!libraryId) return [];
|
||||||
|
libraryIdSchema.validate({libraryId});
|
||||||
|
this.autorun(function (){
|
||||||
|
let userId = this.userId;
|
||||||
|
let library = Libraries.findOne(libraryId);
|
||||||
|
try { assertViewPermission(library, userId) }
|
||||||
|
catch(e){
|
||||||
|
return this.error(e);
|
||||||
|
}
|
||||||
|
return Libraries.find({
|
||||||
|
_id: libraryId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let libraryIdSchema = new SimpleSchema({
|
let libraryIdSchema = new SimpleSchema({
|
||||||
libraryId: {
|
libraryId:{
|
||||||
type: String,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.publish('library', function(libraryId){
|
Meteor.publish('libraryNodes', function(libraryId){
|
||||||
|
if (!libraryId) return [];
|
||||||
libraryIdSchema.validate({libraryId});
|
libraryIdSchema.validate({libraryId});
|
||||||
this.autorun(function (){
|
this.autorun(function (){
|
||||||
let userId = this.userId;
|
let userId = this.userId;
|
||||||
let libraryCursor = Libraries.find({
|
let library = Libraries.findOne(libraryId);
|
||||||
_id: libraryId,
|
|
||||||
});
|
|
||||||
let library = libraryCursor.fetch()[0];
|
|
||||||
try { assertViewPermission(library, userId) }
|
try { assertViewPermission(library, userId) }
|
||||||
catch(e){ return [] }
|
catch(e){
|
||||||
|
return this.error(e);
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
libraryCursor,
|
|
||||||
LibraryNodes.find({
|
LibraryNodes.find({
|
||||||
'ancestors.id': libraryId,
|
'ancestors.id': libraryId,
|
||||||
}, {
|
}, {
|
||||||
|
|||||||
15
app/imports/server/publications/ownedDocuments.js
Normal file
15
app/imports/server/publications/ownedDocuments.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
|
|
||||||
|
Meteor.publish('ownedDocuments', function(){
|
||||||
|
this.autorun(function (){
|
||||||
|
let userId = this.userId;
|
||||||
|
if (!userId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
Creatures.find({owner: userId}),
|
||||||
|
Libraries.find({owner: userId}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
default: 0,
|
||||||
},
|
},
|
||||||
open: Boolean,
|
open: Boolean,
|
||||||
flat: Boolean,
|
flat: Boolean,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
fab
|
fab
|
||||||
small
|
small
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
|
:disabled="disabled"
|
||||||
|
:style="disabled ? 'background-color: #616161 !important;' : ''"
|
||||||
@click="$emit('click')"
|
@click="$emit('click')"
|
||||||
>
|
>
|
||||||
<v-icon>{{ icon }}</v-icon>
|
<v-icon>{{ icon }}</v-icon>
|
||||||
@@ -18,7 +20,7 @@
|
|||||||
* component creates a v-btn with a label.
|
* component creates a v-btn with a label.
|
||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
props: ['icon', 'label'],
|
props: ['icon', 'label', 'disabled'],
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
104
app/imports/ui/creature/character/CharacterSheetFab.vue
Normal file
104
app/imports/ui/creature/character/CharacterSheetFab.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-speed-dial
|
||||||
|
v-if="speedDials && speedDials.length"
|
||||||
|
v-model="fab"
|
||||||
|
direction="bottom"
|
||||||
|
>
|
||||||
|
<template #activator>
|
||||||
|
<v-btn
|
||||||
|
v-model="fab"
|
||||||
|
color="primary"
|
||||||
|
fab
|
||||||
|
data-id="insert-creature-property-fab"
|
||||||
|
small
|
||||||
|
>
|
||||||
|
<v-icon>add</v-icon>
|
||||||
|
<v-icon>close</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<labeled-fab
|
||||||
|
v-for="type in speedDials"
|
||||||
|
:key="type"
|
||||||
|
color="primary"
|
||||||
|
:data-id="`insert-creature-property-type-${type}`"
|
||||||
|
:label="'New ' + properties[type].name"
|
||||||
|
:icon="properties[type].icon"
|
||||||
|
:disabled="!editPermission"
|
||||||
|
@click="insertPropertyOfType(type)"
|
||||||
|
/>
|
||||||
|
</v-speed-dial>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import LabeledFab from '/imports/ui/components/LabeledFab.vue';
|
||||||
|
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
||||||
|
import insertProperty from '/imports/api/creature/creatureProperties/methods/insertProperty.js';
|
||||||
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
'stats',
|
||||||
|
'features',
|
||||||
|
'inventory',
|
||||||
|
'spells',
|
||||||
|
'character',
|
||||||
|
'tree',
|
||||||
|
];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
LabeledFab,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
editPermission: Boolean,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
fab: false,
|
||||||
|
};},
|
||||||
|
computed: {
|
||||||
|
creatureId(){
|
||||||
|
return this.$route.params.id;
|
||||||
|
},
|
||||||
|
tabNumber(){
|
||||||
|
return this.$store.getters.tabById(this.creatureId);
|
||||||
|
},
|
||||||
|
speedDials(){
|
||||||
|
return this.speedDialsByTab[tabs[this.tabNumber]];
|
||||||
|
},
|
||||||
|
speedDialsByTab() { return {
|
||||||
|
'stats': ['attribute', 'skill', 'action', 'attack'],
|
||||||
|
'features': ['feature'],
|
||||||
|
'inventory': ['item', 'container'],
|
||||||
|
'spells': ['spellList', 'spell'],
|
||||||
|
'character': ['note'],
|
||||||
|
};},
|
||||||
|
properties(){
|
||||||
|
return PROPERTIES;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
insertPropertyOfType(type){
|
||||||
|
let that = this;
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'creature-property-creation-dialog',
|
||||||
|
elementId: 'insert-creature-property-type-' + type,
|
||||||
|
data: {
|
||||||
|
forcedType: type,
|
||||||
|
},
|
||||||
|
callback(creatureProperty){
|
||||||
|
if (!creatureProperty) return;
|
||||||
|
creatureProperty.parent = {collection: 'creatures', id: that.creatureId};
|
||||||
|
creatureProperty.ancestors = [ {collection: 'creatures', id: that.creatureId}];
|
||||||
|
setDocToLastOrder({collection: CreatureProperties, doc: creatureProperty});
|
||||||
|
let id = insertProperty.call({creatureProperty});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -5,15 +5,15 @@
|
|||||||
right
|
right
|
||||||
clipped
|
clipped
|
||||||
>
|
>
|
||||||
<log-tab :creature-id="$route.params.id" />
|
<character-log :creature-id="$route.params.id" />
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import LogTab from '/imports/ui/log/CharacterLog.vue';
|
import CharacterLog from '/imports/ui/log/CharacterLog.vue';
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LogTab,
|
CharacterLog,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
drawer: {
|
drawer: {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
:color="toolbarColor"
|
:color="toolbarColor"
|
||||||
:dark="isDark"
|
:dark="isDark"
|
||||||
:light="!isDark"
|
:light="!isDark"
|
||||||
|
extended
|
||||||
tabs
|
tabs
|
||||||
dense
|
dense
|
||||||
>
|
>
|
||||||
@@ -73,11 +74,12 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:key="$route.meta.title"
|
:key="$route.meta.title"
|
||||||
style="width: 100%"
|
class="layout row"
|
||||||
>
|
>
|
||||||
<v-tabs
|
<v-tabs
|
||||||
v-if="creature"
|
v-if="creature"
|
||||||
slot="extension"
|
class="flex"
|
||||||
|
style="min-width: 0"
|
||||||
centered
|
centered
|
||||||
grow
|
grow
|
||||||
max="100px"
|
max="100px"
|
||||||
@@ -106,6 +108,11 @@
|
|||||||
Tree
|
Tree
|
||||||
</v-tab>
|
</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
|
<v-spacer />
|
||||||
|
<character-sheet-fab
|
||||||
|
class="character-sheet-fab"
|
||||||
|
:edit-permission="editPermission"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</v-fade-transition>
|
</v-fade-transition>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
@@ -119,8 +126,15 @@ import { theme } from '/imports/ui/theme.js';
|
|||||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||||
import { updateUserSharePermissions } from '/imports/api/sharing/sharing.js';
|
import { updateUserSharePermissions } from '/imports/api/sharing/sharing.js';
|
||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
|
import CharacterSheetFab from '/imports/ui/creature/character/CharacterSheetFab.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
inject: {
|
||||||
|
context: { default: {} }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
CharacterSheetFab,
|
||||||
|
},
|
||||||
data(){return {
|
data(){return {
|
||||||
theme,
|
theme,
|
||||||
}},
|
}},
|
||||||
@@ -231,4 +245,8 @@ export default {
|
|||||||
.character-sheet-toolbar .v-tabs__bar {
|
.character-sheet-toolbar .v-tabs__bar {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
|
.character-sheet-fab {
|
||||||
|
bottom: -24px;
|
||||||
|
margin-right: -8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<selectable-property-dialog v-model="type">
|
<selectable-property-dialog
|
||||||
<creature-property-insert-form
|
:value="forcedType || type"
|
||||||
:type="type"
|
@input="e => type = e"
|
||||||
:property-name="getPropertyName(type)"
|
>
|
||||||
@back="type = undefined"
|
<creature-property-insert-form
|
||||||
/>
|
:type="forcedType || type"
|
||||||
</selectable-property-dialog>
|
:property-name="getPropertyName(forcedType || type)"
|
||||||
|
@back="back"
|
||||||
|
/>
|
||||||
|
</selectable-property-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -14,15 +17,28 @@ import CreaturePropertyInsertForm from '/imports/ui/creature/creatureProperties/
|
|||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() { return {
|
|
||||||
type: undefined,
|
|
||||||
};},
|
|
||||||
components: {
|
components: {
|
||||||
SelectablePropertyDialog,
|
SelectablePropertyDialog,
|
||||||
CreaturePropertyInsertForm,
|
CreaturePropertyInsertForm,
|
||||||
},
|
},
|
||||||
|
props: {
|
||||||
|
forcedType: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() { return {
|
||||||
|
type: undefined,
|
||||||
|
};},
|
||||||
methods: {
|
methods: {
|
||||||
getPropertyName,
|
getPropertyName,
|
||||||
|
back(){
|
||||||
|
if (this.forcedType){
|
||||||
|
this.$store.dispatch('popDialogStack');
|
||||||
|
} else {
|
||||||
|
this.type = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -70,14 +70,22 @@ export default {
|
|||||||
};},
|
};},
|
||||||
watch: {
|
watch: {
|
||||||
type(newType){
|
type(newType){
|
||||||
if (!newType) return;
|
this.changeType(newType);
|
||||||
this.schema = propertySchemasIndex[newType];
|
|
||||||
this.validationContext = this.schema.newContext();
|
|
||||||
let model = this.schema.clean({});
|
|
||||||
model.type = newType;
|
|
||||||
this.model = model;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted(){
|
||||||
|
this.changeType(this.type);
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
changeType(type){
|
||||||
|
if (!type) return;
|
||||||
|
this.schema = propertySchemasIndex[type];
|
||||||
|
this.validationContext = this.schema.newContext();
|
||||||
|
let model = this.schema.clean({});
|
||||||
|
model.type = type;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -143,10 +143,10 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
|||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
|
||||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
|
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
|
||||||
|
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -243,15 +243,12 @@ export default {
|
|||||||
// the quantity to fill
|
// the quantity to fill
|
||||||
nodes = nodes.filter(node => {
|
nodes = nodes.filter(node => {
|
||||||
if (node.slotFillerCondition){
|
if (node.slotFillerCondition){
|
||||||
let context = new CompilationContext();
|
let {result} = evaluateString({
|
||||||
let conditionResult;
|
string: node.slotFillerCondition,
|
||||||
try {
|
scope: this.creature.variables,
|
||||||
conditionResult = parse(node.slotFillerCondition)
|
fn: 'reduce',
|
||||||
.reduce(this.creature.variables, context);
|
});
|
||||||
} catch (e){
|
if (!result.value) return false;
|
||||||
console.warn(e);
|
|
||||||
}
|
|
||||||
if (conditionResult && !conditionResult.value) return false;
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
node.type === 'slotFiller' &&
|
node.type === 'slotFiller' &&
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default {
|
|||||||
}).map(slot => {
|
}).map(slot => {
|
||||||
if (
|
if (
|
||||||
!this.showHiddenSlots &&
|
!this.showHiddenSlots &&
|
||||||
slot.quantityExpected === 0 &&
|
slot.quantityExpectedResult === 0 &&
|
||||||
slot.hideWhenFull
|
slot.hideWhenFull
|
||||||
){
|
){
|
||||||
slot.children = []
|
slot.children = []
|
||||||
@@ -144,11 +144,12 @@ export default {
|
|||||||
}
|
}
|
||||||
return slot;
|
return slot;
|
||||||
}).filter(slot => !( // Hide full and ignored slots
|
}).filter(slot => !( // Hide full and ignored slots
|
||||||
!this.showHiddenSlots &&
|
!this.showHiddenSlots && (
|
||||||
slot.hideWhenFull &&
|
slot.hideWhenFull &&
|
||||||
slot.quantityExpected > 0 &&
|
slot.quantityExpectedResult > 0 &&
|
||||||
slot.totalFilled >= slot.quantityExpected ||
|
slot.spaceLeft <= 0 ||
|
||||||
slot.ignored
|
slot.ignored
|
||||||
|
)
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import CreaturePropertyCreationDialog from '/imports/ui/creature/creaturePropert
|
|||||||
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
||||||
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'
|
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'
|
||||||
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
||||||
|
import DeleteUserAccountDialog from '/imports/ui/user/DeleteUserAccountDialog.vue';
|
||||||
import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue';
|
import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue';
|
||||||
import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue';
|
import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue';
|
||||||
import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
||||||
@@ -26,6 +27,7 @@ export default {
|
|||||||
CreaturePropertyDialog,
|
CreaturePropertyDialog,
|
||||||
CreaturePropertyFromLibraryDialog,
|
CreaturePropertyFromLibraryDialog,
|
||||||
DeleteConfirmationDialog,
|
DeleteConfirmationDialog,
|
||||||
|
DeleteUserAccountDialog,
|
||||||
ExperienceInsertDialog,
|
ExperienceInsertDialog,
|
||||||
ExperienceListDialog,
|
ExperienceListDialog,
|
||||||
InviteDialog,
|
InviteDialog,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
name="toolbar"
|
name="toolbar"
|
||||||
/>
|
/>
|
||||||
<v-toolbar
|
<v-toolbar
|
||||||
v-if="!$route.matched[0].components.toolbar"
|
v-if="!$route.matched[0] || !$route.matched[0].components.toolbar"
|
||||||
app
|
app
|
||||||
color="secondary"
|
color="secondary"
|
||||||
dark
|
dark
|
||||||
|
|||||||
@@ -19,13 +19,27 @@
|
|||||||
>
|
>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-switch
|
<v-switch
|
||||||
|
v-if="!libraryId || canEditLibrary"
|
||||||
v-model="organize"
|
v-model="organize"
|
||||||
label="Organize"
|
label="Organize"
|
||||||
class="mx-3"
|
class="mx-3"
|
||||||
style="flex-grow: 0; height: 32px;"
|
style="flex-grow: 0; height: 32px;"
|
||||||
/>
|
/>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
<div
|
||||||
|
v-if="libraryId"
|
||||||
|
style="width: 100%; height: 100%; overflow: auto;"
|
||||||
|
>
|
||||||
|
<library-contents-container
|
||||||
|
:library-id="libraryId"
|
||||||
|
:organize-mode="organize"
|
||||||
|
:selected-node-id="selected"
|
||||||
|
should-subscribe
|
||||||
|
@selected="clickNode"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<library-browser
|
<library-browser
|
||||||
|
v-else
|
||||||
edit-mode
|
edit-mode
|
||||||
:organize-mode="organize"
|
:organize-mode="organize"
|
||||||
:selected-node-id="selected"
|
:selected-node-id="selected"
|
||||||
@@ -53,17 +67,24 @@ import LibraryBrowser from '/imports/ui/library/LibraryBrowser.vue';
|
|||||||
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
import LibraryNodeDialog from '/imports/ui/library/LibraryNodeDialog.vue';
|
||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
|
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||||
|
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TreeDetailLayout,
|
TreeDetailLayout,
|
||||||
LibraryBrowser,
|
LibraryBrowser,
|
||||||
LibraryNodeDialog,
|
LibraryNodeDialog,
|
||||||
|
LibraryContentsContainer,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
selection: Boolean,
|
selection: Boolean,
|
||||||
|
libraryId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
organize: false,
|
organize: false,
|
||||||
@@ -113,13 +134,33 @@ export default {
|
|||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
$subscribe: {
|
$subscribe: {
|
||||||
'libraries': [],
|
'library'(){
|
||||||
|
if (this.libraryId){
|
||||||
|
return [this.libraryId]
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
libraries(){
|
libraries(){
|
||||||
return Libraries.find({}, {
|
return Libraries.find({}, {
|
||||||
sort: {name: 1}
|
sort: {name: 1}
|
||||||
}).fetch();
|
}).fetch();
|
||||||
},
|
},
|
||||||
|
library(){
|
||||||
|
let libraryId = this.libraryId;
|
||||||
|
if (!libraryId) return;
|
||||||
|
return Libraries.findOne(libraryId);
|
||||||
|
},
|
||||||
|
canEditLibrary(){
|
||||||
|
if (!this.libraryId) return;
|
||||||
|
try {
|
||||||
|
assertEditPermission(this.library, Meteor.userId());
|
||||||
|
return true;
|
||||||
|
} catch (e){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
selectedNode(){
|
selectedNode(){
|
||||||
return LibraryNodes.findOne({
|
return LibraryNodes.findOne({
|
||||||
_id: this.selected,
|
_id: this.selected,
|
||||||
|
|||||||
@@ -6,13 +6,13 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<v-expansion-panel
|
<v-expansion-panel
|
||||||
v-model="expandedLibrary"
|
|
||||||
style="box-shadow: none;"
|
style="box-shadow: none;"
|
||||||
expand
|
expand
|
||||||
>
|
>
|
||||||
<v-expansion-panel-content
|
<v-expansion-panel-content
|
||||||
v-for="library in libraries"
|
v-for="(library, index) in libraries"
|
||||||
:key="library._id"
|
:key="library._id"
|
||||||
|
v-model="expandedLibrary[index]"
|
||||||
lazy
|
lazy
|
||||||
:data-id="library._id"
|
:data-id="library._id"
|
||||||
>
|
>
|
||||||
@@ -24,9 +24,10 @@
|
|||||||
<v-card flat>
|
<v-card flat>
|
||||||
<library-contents-container
|
<library-contents-container
|
||||||
:library-id="library._id"
|
:library-id="library._id"
|
||||||
:organize-mode="organizeMode"
|
:organize-mode="organizeMode && editPermission(library)"
|
||||||
:edit-mode="editMode"
|
:edit-mode="editMode"
|
||||||
:selected-node-id="selectedNodeId"
|
:selected-node-id="selectedNodeId"
|
||||||
|
:should-subscribe="expandedLibrary[index]"
|
||||||
@selected="e => $emit('selected', e)"
|
@selected="e => $emit('selected', e)"
|
||||||
/>
|
/>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
@@ -47,16 +48,16 @@
|
|||||||
small
|
small
|
||||||
icon
|
icon
|
||||||
:disabled="!editPermission(library)"
|
:disabled="!editPermission(library)"
|
||||||
@click="editLibrary(library._id)"
|
@click="$router.push(`/library/${library._id}`)"
|
||||||
>
|
>
|
||||||
<v-icon>create</v-icon>
|
<v-icon>arrow_forward</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-expansion-panel-content>
|
</v-expansion-panel-content>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-show="expandedLibrary === null"
|
v-show="noLibrariesExpanded"
|
||||||
v-if="editMode"
|
v-if="editMode"
|
||||||
flat
|
flat
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -86,11 +87,25 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
organizeMode: Boolean,
|
organizeMode: Boolean,
|
||||||
editMode: Boolean,
|
editMode: Boolean,
|
||||||
selectedNodeId: String,
|
selectedNodeId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
expandedLibrary: null,
|
expandedLibrary: [],
|
||||||
|
expandedLibraryContent: [],
|
||||||
};},
|
};},
|
||||||
|
computed: {
|
||||||
|
noLibrariesExpanded(){
|
||||||
|
if (!this.expandedLibrary) return true;
|
||||||
|
let noneExpanded = true;
|
||||||
|
this.expandedLibrary.forEach(lib => {
|
||||||
|
if(lib) noneExpanded = false;
|
||||||
|
});
|
||||||
|
return noneExpanded;
|
||||||
|
},
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
$subscribe: {
|
$subscribe: {
|
||||||
'libraries': [],
|
'libraries': [],
|
||||||
@@ -106,6 +121,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
log: console.log,
|
||||||
insertLibrary(){
|
insertLibrary(){
|
||||||
if (this.paidBenefits){
|
if (this.paidBenefits){
|
||||||
this.$store.commit('pushDialogStack', {
|
this.$store.commit('pushDialogStack', {
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<tree-node-list
|
<v-fade-transition
|
||||||
group="library"
|
hide-on-leave
|
||||||
:children="libraryChildren"
|
>
|
||||||
:organize="organizeMode"
|
<tree-node-list
|
||||||
:selected-node-id="selectedNodeId"
|
v-if="slowShouldSubscribe && $subReady.libraryNodes"
|
||||||
@selected="e => $emit('selected', e)"
|
group="library"
|
||||||
@reordered="reordered"
|
:children="libraryChildren"
|
||||||
@reorganized="reorganized"
|
:organize="organizeMode"
|
||||||
/>
|
:selected-node-id="selectedNodeId"
|
||||||
|
@selected="e => $emit('selected', e)"
|
||||||
|
@reordered="reordered"
|
||||||
|
@reorganized="reorganized"
|
||||||
|
/>
|
||||||
|
<v-layout
|
||||||
|
v-else
|
||||||
|
row
|
||||||
|
align-center
|
||||||
|
justify-center
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
<v-progress-circular
|
||||||
|
color="primary"
|
||||||
|
:indeterminate="slowShouldSubscribe"
|
||||||
|
/>
|
||||||
|
</v-layout>
|
||||||
|
</v-fade-transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -25,13 +42,36 @@
|
|||||||
libraryId: String,
|
libraryId: String,
|
||||||
organizeMode: Boolean,
|
organizeMode: Boolean,
|
||||||
selectedNodeId: String,
|
selectedNodeId: String,
|
||||||
|
shouldSubscribe: Boolean,
|
||||||
},
|
},
|
||||||
|
data(){return {
|
||||||
|
slowShouldSubscribe: this.shouldSubscribe,
|
||||||
|
};},
|
||||||
|
watch:{
|
||||||
|
shouldSubscribe(newValue){
|
||||||
|
if (this.timeoutId){
|
||||||
|
clearTimeout(this.timeoutId);
|
||||||
|
delete this.timeoutId;
|
||||||
|
}
|
||||||
|
if (newValue){
|
||||||
|
this.slowShouldSubscribe = newValue
|
||||||
|
} else {
|
||||||
|
this.timeoutId = setTimeout(()=>{
|
||||||
|
this.slowShouldSubscribe = newValue
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
$subscribe: {
|
$subscribe: {
|
||||||
'library'(){
|
'libraryNodes'(){
|
||||||
return [this.libraryId]
|
if (this.slowShouldSubscribe){
|
||||||
},
|
return [this.libraryId];
|
||||||
},
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
library(){
|
library(){
|
||||||
return Libraries.findOne(this.libraryId);
|
return Libraries.findOne(this.libraryId);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
<template lang="html">
|
|
||||||
<div
|
|
||||||
class="layout row"
|
|
||||||
style="background-color: inherit;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="layout column"
|
|
||||||
style="
|
|
||||||
background-color: inherit;
|
|
||||||
width: initial;
|
|
||||||
max-width: 100%;
|
|
||||||
min-width: 320px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<v-toolbar
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
>
|
|
||||||
<v-spacer />
|
|
||||||
<v-switch
|
|
||||||
v-model="organize"
|
|
||||||
label="Organize"
|
|
||||||
class="mx-3"
|
|
||||||
style="flex-grow: 0; height: 32px;"
|
|
||||||
/>
|
|
||||||
</v-toolbar>
|
|
||||||
<library-contents-container
|
|
||||||
:library-id="$route.params.id"
|
|
||||||
:organize-mode="organize"
|
|
||||||
:selected-node-id="selected"
|
|
||||||
@selected="e => selected = e"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<v-divider vertical />
|
|
||||||
<div
|
|
||||||
style="width: 100%; background-color: inherit;"
|
|
||||||
data-id="selected-node-card"
|
|
||||||
>
|
|
||||||
<v-toolbar
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
>
|
|
||||||
<property-icon
|
|
||||||
:model="selectedNode"
|
|
||||||
class="mr-2"
|
|
||||||
/>
|
|
||||||
<div class="title">
|
|
||||||
{{ getPropertyName(selectedNode && selectedNode.type) }}
|
|
||||||
</div>
|
|
||||||
<v-spacer />
|
|
||||||
<v-btn
|
|
||||||
v-if="selectedNode"
|
|
||||||
flat
|
|
||||||
icon
|
|
||||||
@click="editLibraryNode"
|
|
||||||
>
|
|
||||||
<v-icon>create</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-toolbar>
|
|
||||||
<v-card-text style="overflow-y: auto;">
|
|
||||||
<property-viewer :model="selectedNode" />
|
|
||||||
</v-card-text>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import PropertyViewer from '/imports/ui/properties/shared/PropertyViewer.vue';
|
|
||||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
|
||||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
|
||||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
|
||||||
import LibraryContentsContainer from '/imports/ui/library/LibraryContentsContainer.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
LibraryContentsContainer,
|
|
||||||
PropertyViewer,
|
|
||||||
PropertyIcon,
|
|
||||||
},
|
|
||||||
data(){ return {
|
|
||||||
organize: false,
|
|
||||||
selected: undefined,
|
|
||||||
};},
|
|
||||||
watch:{
|
|
||||||
selectedNode(val){
|
|
||||||
this.$emit('selected', val)
|
|
||||||
},
|
|
||||||
'library.name'(value){
|
|
||||||
this.$store.commit('setPageTitle', value || 'Library');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
editLibraryNode(){
|
|
||||||
this.$store.commit('pushDialogStack', {
|
|
||||||
component: 'library-node-edit-dialog',
|
|
||||||
elementId: 'selected-node-card',
|
|
||||||
data: {_id: this.selected},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getPropertyName,
|
|
||||||
},
|
|
||||||
meteor: {
|
|
||||||
$subscribe: {
|
|
||||||
'libraries': [],
|
|
||||||
},
|
|
||||||
library(){
|
|
||||||
return Libraries.findOne(this.$route.params.id);
|
|
||||||
},
|
|
||||||
selectedNode(){
|
|
||||||
return LibraryNodes.findOne({
|
|
||||||
_id: this.selected,
|
|
||||||
removed: {$ne: true}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
</style>
|
|
||||||
@@ -1,28 +1,53 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-toolbar-items>
|
<v-toolbar
|
||||||
<v-btn
|
app
|
||||||
v-if="showSubscribeButton"
|
color="secondary"
|
||||||
flat
|
dark
|
||||||
:loading="loading"
|
tabs
|
||||||
@click="subscribe(!subscribed)"
|
extended
|
||||||
>
|
dense
|
||||||
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
>
|
||||||
</v-btn>
|
<v-toolbar-side-icon @click="toggleDrawer" />
|
||||||
<v-btn
|
<v-toolbar-items>
|
||||||
v-if="canEdit"
|
<v-btn
|
||||||
flat
|
flat
|
||||||
icon
|
icon
|
||||||
data-id="library-edit-button"
|
@click="$router.push('/library')"
|
||||||
@click="editLibrary(library._id)"
|
>
|
||||||
>
|
<v-icon>arrow_back</v-icon>
|
||||||
<v-icon>create</v-icon>
|
</v-btn>
|
||||||
</v-btn>
|
</v-toolbar-items>
|
||||||
</v-toolbar-items>
|
<v-toolbar-title>
|
||||||
|
{{ library && library.name }}
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer />
|
||||||
|
<v-toolbar-items>
|
||||||
|
<v-btn
|
||||||
|
v-if="showSubscribeButton"
|
||||||
|
flat
|
||||||
|
:loading="loading"
|
||||||
|
@click="subscribe(!subscribed)"
|
||||||
|
>
|
||||||
|
{{ subscribed ? 'Unsubscribe' : 'Subscribe' }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-if="canEdit"
|
||||||
|
flat
|
||||||
|
icon
|
||||||
|
data-id="library-edit-button"
|
||||||
|
@click="editLibrary(library._id)"
|
||||||
|
>
|
||||||
|
<v-icon>settings</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-toolbar-items>
|
||||||
|
</v-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Libraries from '/imports/api/library/Libraries.js';
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||||
|
import { mapMutations } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data(){ return {
|
data(){ return {
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -33,8 +58,10 @@ export default {
|
|||||||
},
|
},
|
||||||
subscribed(){
|
subscribed(){
|
||||||
let libraryId = this.$route.params.id;
|
let libraryId = this.$route.params.id;
|
||||||
let subs = Meteor.user().subscribedLibraries;
|
let user = Meteor.user();
|
||||||
return subs.includes(libraryId);
|
if (!user) return false;
|
||||||
|
let subs = user.subscribedLibraries;
|
||||||
|
return subs && subs.includes(libraryId);
|
||||||
},
|
},
|
||||||
showSubscribeButton(){
|
showSubscribeButton(){
|
||||||
let userId = Meteor.userId();
|
let userId = Meteor.userId();
|
||||||
@@ -60,6 +87,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapMutations([
|
||||||
|
'toggleDrawer',
|
||||||
|
]),
|
||||||
subscribe(value){
|
subscribe(value){
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
Meteor.users.subscribeToLibrary.call({
|
Meteor.users.subscribeToLibrary.call({
|
||||||
@@ -3,8 +3,10 @@
|
|||||||
style="height: 100%; overflow: hidden;"
|
style="height: 100%; overflow: hidden;"
|
||||||
class="character-log layout column justify-end"
|
class="character-log layout column justify-end"
|
||||||
>
|
>
|
||||||
<div
|
<v-slide-y-reverse-transition
|
||||||
class="log flex layout column reverse align-end pa-3"
|
group
|
||||||
|
hide-on-leave
|
||||||
|
class="log-entries flex layout column reverse align-end pa-3"
|
||||||
style="overflow: auto;"
|
style="overflow: auto;"
|
||||||
>
|
>
|
||||||
<log-entry
|
<log-entry
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
:key="log._id"
|
:key="log._id"
|
||||||
:model="log"
|
:model="log"
|
||||||
/>
|
/>
|
||||||
</div>
|
</v-slide-y-reverse-transition>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="input"
|
v-model="input"
|
||||||
@@ -120,4 +122,10 @@ export default {
|
|||||||
.log-tab p:last-child {
|
.log-tab p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
.theme--dark .log-entries {
|
||||||
|
background: #303030;
|
||||||
|
}
|
||||||
|
.log-entries {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -141,6 +141,19 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-list>
|
</v-list>
|
||||||
</template>
|
</template>
|
||||||
|
<v-layout
|
||||||
|
row
|
||||||
|
justify-end
|
||||||
|
class="mt-3"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
color="error"
|
||||||
|
data-id="delete-account-btn"
|
||||||
|
@click="deleteAccount"
|
||||||
|
>
|
||||||
|
Delete Account
|
||||||
|
</v-btn>
|
||||||
|
</v-layout>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -270,6 +283,12 @@
|
|||||||
this.updatePatreonLoading = false;
|
this.updatePatreonLoading = false;
|
||||||
if (error) this.updatePatreonError = error;
|
if (error) this.updatePatreonError = error;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
deleteAccount(){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'delete-user-account-dialog',
|
||||||
|
elementId: 'delete-account-btn',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<single-card-layout>
|
<single-card-layout>
|
||||||
<library-and-node />
|
<library-and-node
|
||||||
|
:library-id="$route.params.id"
|
||||||
|
/>
|
||||||
</single-card-layout>
|
</single-card-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<template lang="html">
|
|
||||||
<div>
|
|
||||||
<v-card class="ma-4">
|
|
||||||
<single-library />
|
|
||||||
</v-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import SingleLibrary from '/imports/ui/library/SingleLibrary.vue';
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
SingleLibrary,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -146,7 +146,7 @@ export default {
|
|||||||
return Math.max(this.model.usesResult, 0);
|
return Math.max(this.model.usesResult, 0);
|
||||||
},
|
},
|
||||||
usesLeft(){
|
usesLeft(){
|
||||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
return Math.max(this.model.usesResult - (this.model.usesUsed || 0), 0);
|
||||||
},
|
},
|
||||||
propertyName(){
|
propertyName(){
|
||||||
return getPropertyName(this.model.type);
|
return getPropertyName(this.model.type);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="resources-form">
|
<div class="resources-form">
|
||||||
<div
|
<div
|
||||||
v-if="model.attributesConsumed.length"
|
v-if="model.attributesConsumed && model.attributesConsumed.length"
|
||||||
class="subheading"
|
class="subheading"
|
||||||
>
|
>
|
||||||
Attributes
|
Attributes
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<v-toolbar-title slot="toolbar">
|
<v-toolbar-title slot="toolbar">
|
||||||
Add Library Content
|
Add Library Content
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<property-selector @select="property => $emit('input', property)" />
|
<property-selector @select="type => $emit('input', type)" />
|
||||||
</dialog-base>
|
</dialog-base>
|
||||||
<div
|
<div
|
||||||
v-show="value"
|
v-show="value"
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ export default {
|
|||||||
return Math.max(this.model.usesResult, 0);
|
return Math.max(this.model.usesResult, 0);
|
||||||
},
|
},
|
||||||
usesLeft(){
|
usesLeft(){
|
||||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
return Math.max(this.model.usesResult - (this.model.usesUsed || 0), 0);
|
||||||
},
|
},
|
||||||
actionTypeIcon() {
|
actionTypeIcon() {
|
||||||
return `$vuetify.icons.${this.model.actionType}`;
|
return `$vuetify.icons.${this.model.actionType}`;
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import Home from '/imports/ui/pages/Home.vue';
|
|||||||
import About from '/imports/ui/pages/About.vue';
|
import About from '/imports/ui/pages/About.vue';
|
||||||
import CharacterList from '/imports/ui/pages/CharacterList.vue';
|
import CharacterList from '/imports/ui/pages/CharacterList.vue';
|
||||||
import Library from '/imports/ui/pages/Library.vue';
|
import Library from '/imports/ui/pages/Library.vue';
|
||||||
import SingleLibraryPage from '/imports/ui/pages/SingleLibraryPage.vue'
|
import SingleLibraryToolbar from '/imports/ui/library/SingleLibraryToolbar.vue';
|
||||||
import SingleLibraryToolbarItems from '/imports/ui/library/SingleLibraryToolbarItems.vue'
|
|
||||||
import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue';
|
import CharacterSheetPage from '/imports/ui/pages/CharacterSheetPage.vue';
|
||||||
import CharacterSheetToolbar from '/imports/ui/creature/character/CharacterSheetToolbar.vue';
|
import CharacterSheetToolbar from '/imports/ui/creature/character/CharacterSheetToolbar.vue';
|
||||||
import CharacterSheetRightDrawer from '/imports/ui/creature/character/CharacterSheetRightDrawer.vue';
|
import CharacterSheetRightDrawer from '/imports/ui/creature/character/CharacterSheetRightDrawer.vue';
|
||||||
@@ -123,8 +122,8 @@ RouterFactory.configure(factory => {
|
|||||||
name: 'singleLibrary',
|
name: 'singleLibrary',
|
||||||
path: '/library/:id',
|
path: '/library/:id',
|
||||||
components: {
|
components: {
|
||||||
default: SingleLibraryPage,
|
default: Library,
|
||||||
toolbarItems: SingleLibraryToolbarItems,
|
toolbar: SingleLibraryToolbar,
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
title: 'Library',
|
title: 'Library',
|
||||||
|
|||||||
153
app/imports/ui/user/DeleteUserAccountDialog.vue
Normal file
153
app/imports/ui/user/DeleteUserAccountDialog.vue
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<v-toolbar-title slot="toolbar">
|
||||||
|
Delete User Account
|
||||||
|
</v-toolbar-title>
|
||||||
|
<div>
|
||||||
|
<h2>Are you sure you want to delete your account?</h2>
|
||||||
|
<v-alert
|
||||||
|
:value="true"
|
||||||
|
icon="warning"
|
||||||
|
color="error"
|
||||||
|
outline
|
||||||
|
>
|
||||||
|
Deleted accounts can not be recovered
|
||||||
|
</v-alert>
|
||||||
|
<p>We will immediately delete your account and all of your data</p>
|
||||||
|
<p>Your username will become available to anyone on DiceCloud</p>
|
||||||
|
<template v-if="characters.length">
|
||||||
|
<h3 v-if="characters.length > 1">
|
||||||
|
These {{ characters.length }} characters will be deleted:
|
||||||
|
</h3>
|
||||||
|
<h3 v-else>
|
||||||
|
This character will be deleted:
|
||||||
|
</h3>
|
||||||
|
<v-list>
|
||||||
|
<creature-list-tile
|
||||||
|
v-for="character in characters"
|
||||||
|
:key="character._id"
|
||||||
|
:model="character"
|
||||||
|
/>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
<template v-if="libraries.length">
|
||||||
|
<h3 v-if="libraries.length > 1">
|
||||||
|
These {{ libraries.length }} libraries will be deleted:
|
||||||
|
</h3>
|
||||||
|
<h3 v-else>
|
||||||
|
This library will be deleted:
|
||||||
|
</h3>
|
||||||
|
<v-list>
|
||||||
|
<creature-list-tile
|
||||||
|
v-for="library in libraries"
|
||||||
|
:key="library._id"
|
||||||
|
:model="library"
|
||||||
|
/>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
<v-layout
|
||||||
|
column
|
||||||
|
align-start
|
||||||
|
>
|
||||||
|
<v-text-field
|
||||||
|
v-if="user.username"
|
||||||
|
v-model="usernameInput"
|
||||||
|
label="Type your username or email"
|
||||||
|
style="width: 350px;"
|
||||||
|
:error-messages="usernameInputValid ? undefined : ' '"
|
||||||
|
:append-icon="usernameInputValid ? 'done' : undefined"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="verificationInput"
|
||||||
|
label="To verify type 'delete my account'"
|
||||||
|
style="width: 350px;"
|
||||||
|
:error-messages="verificationInputValid ? undefined : ' '"
|
||||||
|
:append-icon="verificationInputValid ? 'done' : undefined"
|
||||||
|
/>
|
||||||
|
<v-btn
|
||||||
|
class="mt-4"
|
||||||
|
color="error"
|
||||||
|
:disabled="!valid"
|
||||||
|
@click="deleteAccount"
|
||||||
|
>
|
||||||
|
Permanently delete account
|
||||||
|
</v-btn>
|
||||||
|
</v-layout>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
slot="actions"
|
||||||
|
class="layout row justify-end"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
@click="$store.dispatch('popDialogStack')"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import Libraries from '/imports/api/library/Libraries.js';
|
||||||
|
import CreatureListTile from '/imports/ui/creature/CreatureListTile.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
CreatureListTile,
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
usernameInput: '',
|
||||||
|
verificationInput: '',
|
||||||
|
};},
|
||||||
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'ownedDocuments'(){
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
characters(){
|
||||||
|
return Creatures.find({owner: Meteor.userId()});
|
||||||
|
},
|
||||||
|
libraries(){
|
||||||
|
return Libraries.find({owner: Meteor.userId()});
|
||||||
|
},
|
||||||
|
user(){
|
||||||
|
return Meteor.user();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
usernameInputValid(){
|
||||||
|
let username = this.user.username;
|
||||||
|
if (!username) return true;
|
||||||
|
let input = this.usernameInput;
|
||||||
|
if (!input) return false;
|
||||||
|
if (input.toLowerCase() === username.toLowerCase()){
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verificationInputValid(){
|
||||||
|
let input = this.verificationInput || '';
|
||||||
|
return input.toLowerCase() === 'delete my account'
|
||||||
|
},
|
||||||
|
valid(){
|
||||||
|
return this.usernameInputValid && this.verificationInputValid;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteAccount(){
|
||||||
|
this.$router.push('/');
|
||||||
|
Meteor.users.deleteMyAccount.call();
|
||||||
|
this.$store.dispatch('popDialogStack');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -25,7 +25,8 @@ for (const name in SVG_ICONS) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vue.use(VueMeteorTracker);
|
Vue.use(VueMeteorTracker);
|
||||||
Vue.config.meteor.freeze = true
|
Vue.config.meteor.freeze = true;
|
||||||
|
Vue.config.devtools = true;
|
||||||
Vue.use(Vuetify, {
|
Vue.use(Vuetify, {
|
||||||
theme,
|
theme,
|
||||||
iconfont: 'md',
|
iconfont: 'md',
|
||||||
|
|||||||
Reference in New Issue
Block a user