Applied style rules to genocide all \t characters
This commit is contained in:
@@ -2,38 +2,38 @@
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width initial-scale=1.0, user-scalable=no">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png?v=lk6WXp6Pmj">
|
||||
<link rel="icon" type="image/png" href="/favicon-32x32.png?v=lk6WXp6Pmj" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/favicon-194x194.png?v=lk6WXp6Pmj" sizes="194x194">
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png?v=lk6WXp6Pmj" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="/android-chrome-192x192.png?v=lk6WXp6Pmj" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="/favicon-16x16.png?v=lk6WXp6Pmj" sizes="16x16">
|
||||
<link rel="manifest" href="/manifest.json?v=lk6WXp6Pmj">
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=lk6WXp6Pmj">
|
||||
<meta name="msapplication-TileColor" content="#b91d1d">
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png?v=lk6WXp6Pmj">
|
||||
<meta name="theme-color" content="#d12929">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png?v=lk6WXp6Pmj">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png?v=lk6WXp6Pmj">
|
||||
<link rel="icon" type="image/png" href="/favicon-32x32.png?v=lk6WXp6Pmj" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="/favicon-194x194.png?v=lk6WXp6Pmj" sizes="194x194">
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png?v=lk6WXp6Pmj" sizes="96x96">
|
||||
<link rel="icon" type="image/png" href="/android-chrome-192x192.png?v=lk6WXp6Pmj" sizes="192x192">
|
||||
<link rel="icon" type="image/png" href="/favicon-16x16.png?v=lk6WXp6Pmj" sizes="16x16">
|
||||
<link rel="manifest" href="/manifest.json?v=lk6WXp6Pmj">
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=lk6WXp6Pmj">
|
||||
<meta name="msapplication-TileColor" content="#b91d1d">
|
||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png?v=lk6WXp6Pmj">
|
||||
<meta name="theme-color" content="#d12929">
|
||||
|
||||
<style type="text/css" media="print">
|
||||
@page {
|
||||
margin: 0mm;
|
||||
}
|
||||
html {
|
||||
margin: 0px;
|
||||
}
|
||||
* {
|
||||
-webkit-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css" media="print">
|
||||
@page {
|
||||
margin: 0mm;
|
||||
}
|
||||
html {
|
||||
margin: 0px;
|
||||
}
|
||||
* {
|
||||
-webkit-transition: none !important;
|
||||
transition: none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -8,7 +8,7 @@ let creatureFolderSchema = new SimpleSchema({
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
creatures: {
|
||||
type: Array,
|
||||
@@ -21,16 +21,16 @@ let creatureFolderSchema = new SimpleSchema({
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
index: 1,
|
||||
},
|
||||
archived: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
archived: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
});
|
||||
|
||||
CreatureFolders.attachSchema(creatureFolderSchema);
|
||||
|
||||
@@ -18,23 +18,23 @@ let CreaturePropertySchema = new SimpleSchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
type: {
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: Object.keys(propertySchemasIndex),
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
icon: {
|
||||
type: storedIconsSchema,
|
||||
optional: true,
|
||||
@@ -93,20 +93,20 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({
|
||||
|
||||
CreaturePropertySchema.extend(DenormalisedOnlyCreaturePropertySchema);
|
||||
|
||||
for (let key in propertySchemasIndex){
|
||||
let schema = new SimpleSchema({});
|
||||
schema.extend(propertySchemasIndex[key]);
|
||||
schema.extend(CreaturePropertySchema);
|
||||
for (let key in propertySchemasIndex) {
|
||||
let schema = new SimpleSchema({});
|
||||
schema.extend(propertySchemasIndex[key]);
|
||||
schema.extend(CreaturePropertySchema);
|
||||
schema.extend(ColorSchema);
|
||||
schema.extend(ChildSchema);
|
||||
schema.extend(SoftRemovableSchema);
|
||||
CreatureProperties.attachSchema(schema, {
|
||||
selector: {type: key}
|
||||
});
|
||||
schema.extend(ChildSchema);
|
||||
schema.extend(SoftRemovableSchema);
|
||||
CreatureProperties.attachSchema(schema, {
|
||||
selector: { type: key }
|
||||
});
|
||||
}
|
||||
|
||||
export default CreatureProperties;
|
||||
export {
|
||||
DenormalisedOnlyCreaturePropertySchema,
|
||||
CreaturePropertySchema,
|
||||
CreaturePropertySchema,
|
||||
};
|
||||
|
||||
@@ -20,33 +20,33 @@ const adjustQuantity = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, operation, value}) {
|
||||
run({ _id, operation, value }) {
|
||||
// Permissions
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Do work
|
||||
adjustQuantityWork({property, operation, value});
|
||||
adjustQuantityWork({ property, operation, value });
|
||||
},
|
||||
});
|
||||
|
||||
export function adjustQuantityWork({property, operation, value}){
|
||||
export function adjustQuantityWork({ property, operation, value }) {
|
||||
// Check if property has quantity
|
||||
let schema = CreatureProperties.simpleSchema(property);
|
||||
if (!schema.allowsKey('quantity')){
|
||||
if (!schema.allowsKey('quantity')) {
|
||||
throw new Meteor.Error(
|
||||
'Adjust quantity failed',
|
||||
`Property of type "${property.type}" doesn't have a quantity`
|
||||
);
|
||||
}
|
||||
if (operation === 'set'){
|
||||
if (operation === 'set') {
|
||||
CreatureProperties.update(property._id, {
|
||||
$set: {quantity: value, dirty: true}
|
||||
$set: { quantity: value, dirty: true }
|
||||
}, {
|
||||
selector: property
|
||||
});
|
||||
} else if (operation === 'increment'){
|
||||
} else if (operation === 'increment') {
|
||||
// value here is 'damage'
|
||||
value = -value;
|
||||
let currentQuantity = property.quantity;
|
||||
|
||||
@@ -22,7 +22,7 @@ const damageProperty = new ValidatedMethod({
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, operation, value }) {
|
||||
|
||||
|
||||
// Get action context
|
||||
let prop = CreatureProperties.findOne(_id);
|
||||
if (!prop) throw new Meteor.Error(
|
||||
@@ -30,17 +30,17 @@ const damageProperty = new ValidatedMethod({
|
||||
);
|
||||
const creatureId = prop.ancestors[0].id;
|
||||
const actionContext = new ActionContext(creatureId, [creatureId], this);
|
||||
|
||||
// Check permissions
|
||||
|
||||
// Check permissions
|
||||
assertEditPermission(actionContext.creature, this.userId);
|
||||
|
||||
// Check if property can take damage
|
||||
let schema = CreatureProperties.simpleSchema(prop);
|
||||
if (!schema.allowsKey('damage')){
|
||||
throw new Meteor.Error(
|
||||
'Damage property failed',
|
||||
`Property of type "${prop.type}" can't be damaged`
|
||||
);
|
||||
// Check if property can take damage
|
||||
let schema = CreatureProperties.simpleSchema(prop);
|
||||
if (!schema.allowsKey('damage')) {
|
||||
throw new Meteor.Error(
|
||||
'Damage property failed',
|
||||
`Property of type "${prop.type}" can't be damaged`
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the prop by its actionContext counterpart if possible
|
||||
@@ -50,9 +50,9 @@ const damageProperty = new ValidatedMethod({
|
||||
prop = actionContextProp;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
||||
|
||||
|
||||
// Insert the log
|
||||
actionContext.writeLog();
|
||||
return result;
|
||||
@@ -86,7 +86,7 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
||||
}
|
||||
|
||||
let damage, newValue, increment;
|
||||
if (operation === 'set'){
|
||||
if (operation === 'set') {
|
||||
const total = prop.total || 0;
|
||||
// Set represents what we want the value to be after damage
|
||||
// So we need the actual damage to get to that value
|
||||
@@ -105,7 +105,7 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
||||
// Also write it straight to the prop so that it is updated in the actionContext
|
||||
prop.damage = damage;
|
||||
prop.value = newValue;
|
||||
} else if (operation === 'increment'){
|
||||
} else if (operation === 'increment') {
|
||||
let currentValue = prop.value || 0;
|
||||
let currentDamage = prop.damage || 0;
|
||||
increment = value;
|
||||
|
||||
@@ -5,12 +5,12 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import {
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
} from '/imports/api/parenting/parenting.js';
|
||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||
var snackbar;
|
||||
if (Meteor.isClient){
|
||||
if (Meteor.isClient) {
|
||||
snackbar = require(
|
||||
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
||||
).snackbar
|
||||
@@ -31,7 +31,7 @@ const duplicateProperty = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}) {
|
||||
run({ _id }) {
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let creature = getRootCreatureAncestor(property);
|
||||
|
||||
@@ -44,17 +44,17 @@ const duplicateProperty = new ValidatedMethod({
|
||||
|
||||
// Get all the descendants
|
||||
let nodes = CreatureProperties.find({
|
||||
'ancestors.id': _id,
|
||||
removed: {$ne: true},
|
||||
}, {
|
||||
'ancestors.id': _id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||
sort: {order: 1},
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
|
||||
// Alert the user if the limit was hit
|
||||
if (nodes.length > DUPLICATE_CHILDREN_LIMIT){
|
||||
if (nodes.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||
nodes.pop();
|
||||
if (Meteor.isClient){
|
||||
if (Meteor.isClient) {
|
||||
snackbar({
|
||||
text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`,
|
||||
});
|
||||
@@ -63,25 +63,25 @@ const duplicateProperty = new ValidatedMethod({
|
||||
|
||||
// re-map all the ancestors
|
||||
setLineageOfDocs({
|
||||
docArray: nodes,
|
||||
newAncestry : [
|
||||
docArray: nodes,
|
||||
newAncestry: [
|
||||
...property.ancestors,
|
||||
{id: propertyId, collection: 'creatureProperties'}
|
||||
{ id: propertyId, collection: 'creatureProperties' }
|
||||
],
|
||||
oldParent : {id: _id, collection: 'creatureProperties'},
|
||||
});
|
||||
oldParent: { id: _id, collection: 'creatureProperties' },
|
||||
});
|
||||
|
||||
// Give the docs new IDs without breaking internal references
|
||||
renewDocIds({docArray: nodes});
|
||||
renewDocIds({ docArray: nodes });
|
||||
|
||||
// Order the root node
|
||||
property.order += 0.5;
|
||||
|
||||
|
||||
// Mark the sheet as needing recompute
|
||||
property.dirty = true;
|
||||
|
||||
// Insert the properties
|
||||
CreatureProperties.batchInsert([property, ...nodes]);
|
||||
CreatureProperties.batchInsert([property, ...nodes]);
|
||||
|
||||
// Tree structure changed by inserts, reorder the tree
|
||||
reorderDocs({
|
||||
|
||||
@@ -10,8 +10,8 @@ import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/
|
||||
// Equipping or unequipping an item will also change its parent
|
||||
const equipItem = new ValidatedMethod({
|
||||
name: 'creatureProperties.equip',
|
||||
validate({_id, equipped}){
|
||||
if (!_id) throw new Meteor.Error('No _id', '_id is required');
|
||||
validate({ _id, equipped }) {
|
||||
if (!_id) throw new Meteor.Error('No _id', '_id is required');
|
||||
if (equipped !== true && equipped !== false) {
|
||||
throw new Meteor.Error('No equipped', 'equipped is required to be true or false');
|
||||
}
|
||||
@@ -21,20 +21,20 @@ const equipItem = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, equipped}) {
|
||||
run({ _id, equipped }) {
|
||||
let item = CreatureProperties.findOne(_id);
|
||||
if (item.type !== 'item') throw new Meteor.Error('wrong type',
|
||||
'Equip and unequip can only be performed on items');
|
||||
'Equip and unequip can only be performed on items');
|
||||
let creature = getRootCreatureAncestor(item);
|
||||
assertEditPermission(creature, this.userId);
|
||||
CreatureProperties.update(_id, {
|
||||
$set: { equipped, dirty: true },
|
||||
}, {
|
||||
selector: {type: 'item'},
|
||||
});
|
||||
selector: { type: 'item' },
|
||||
});
|
||||
let tag = equipped ? BUILT_IN_TAGS.equipment : BUILT_IN_TAGS.carried;
|
||||
let parentRef = getParentRefByTag(creature._id, tag);
|
||||
if (!parentRef) parentRef = {id: creature._id, collection: 'creatures'};
|
||||
if (!parentRef) parentRef = { id: creature._id, collection: 'creatures' };
|
||||
|
||||
organizeDoc.call({
|
||||
docRef: {
|
||||
|
||||
@@ -6,24 +6,24 @@ import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/ge
|
||||
|
||||
const flipToggle = new ValidatedMethod({
|
||||
name: 'creatureProperties.flipToggle',
|
||||
validate({_id}){
|
||||
if (!_id) throw new Meteor.Error('No _id', '_id is required');
|
||||
validate({ _id }) {
|
||||
if (!_id) throw new Meteor.Error('No _id', '_id is required');
|
||||
},
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}) {
|
||||
run({ _id }) {
|
||||
// Permission
|
||||
let property = CreatureProperties.findOne(_id, {
|
||||
fields: {type: 1, ancestors: 1, enabled: 1, disabled: 1}
|
||||
fields: { type: 1, ancestors: 1, enabled: 1, disabled: 1 }
|
||||
});
|
||||
if (property.type !== 'toggle'){
|
||||
if (property.type !== 'toggle') {
|
||||
throw new Meteor.Error('wrong property',
|
||||
'This method can only be applied to toggles');
|
||||
}
|
||||
if (!property.enabled && !property.disabled){
|
||||
if (!property.enabled && !property.disabled) {
|
||||
throw new Meteor.Error('Computed toggle',
|
||||
'Can\'t flip a toggle that is computed')
|
||||
}
|
||||
@@ -32,13 +32,15 @@ const flipToggle = new ValidatedMethod({
|
||||
|
||||
// Invert the current value, disabled is the canonical store of value
|
||||
const currentValue = !property.disabled;
|
||||
CreatureProperties.update(_id, {$set: {
|
||||
enabled: !currentValue,
|
||||
disabled: currentValue,
|
||||
dirty: true,
|
||||
}}, {
|
||||
selector: {type: 'toggle'},
|
||||
});
|
||||
CreatureProperties.update(_id, {
|
||||
$set: {
|
||||
enabled: !currentValue,
|
||||
disabled: currentValue,
|
||||
dirty: true,
|
||||
}
|
||||
}, {
|
||||
selector: { type: 'toggle' },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getHighestOrder } from '/imports/api/parenting/order.js';
|
||||
|
||||
const insertProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.insert',
|
||||
validate: new SimpleSchema({
|
||||
validate: new SimpleSchema({
|
||||
creatureProperty: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
@@ -24,25 +24,25 @@ const insertProperty = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({creatureProperty, parentRef}) {
|
||||
run({ creatureProperty, parentRef }) {
|
||||
// get the new ancestry for the properties
|
||||
let {parentDoc, ancestors} = getAncestry({parentRef});
|
||||
let { parentDoc, ancestors } = getAncestry({ parentRef });
|
||||
|
||||
// Check permission to edit
|
||||
// Check permission to edit
|
||||
let rootCreature;
|
||||
if (parentRef.collection === 'creatures'){
|
||||
if (parentRef.collection === 'creatures') {
|
||||
rootCreature = parentDoc;
|
||||
} else if (parentRef.collection === 'creatureProperties'){
|
||||
} else if (parentRef.collection === 'creatureProperties') {
|
||||
rootCreature = getRootCreatureAncestor(parentDoc);
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
creatureProperty.parent = parentRef;
|
||||
creatureProperty.ancestors = ancestors;
|
||||
|
||||
return insertPropertyWork({
|
||||
return insertPropertyWork({
|
||||
property: creatureProperty,
|
||||
creature: rootCreature,
|
||||
});
|
||||
@@ -75,31 +75,31 @@ const insertPropertyAsChildOfTag = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({creatureProperty, creatureId, tag, tagDefaultName}) {
|
||||
run({ creatureProperty, creatureId, tag, tagDefaultName }) {
|
||||
let parentRef = getParentRefByTag(creatureId, tag);
|
||||
|
||||
if (!parentRef){
|
||||
if (!parentRef) {
|
||||
// Use the creature as the parent and mark that we need to insert the folder first later
|
||||
var insertFolderFirst = true;
|
||||
parentRef = {id: creatureId, collection: 'creatures'};
|
||||
parentRef = { id: creatureId, collection: 'creatures' };
|
||||
}
|
||||
|
||||
// get the new ancestry for the properties
|
||||
let {parentDoc, ancestors} = getAncestry({parentRef});
|
||||
let { parentDoc, ancestors } = getAncestry({ parentRef });
|
||||
|
||||
// Check permission to edit
|
||||
let rootCreature;
|
||||
if (parentRef.collection === 'creatures'){
|
||||
if (parentRef.collection === 'creatures') {
|
||||
rootCreature = parentDoc;
|
||||
} else if (parentRef.collection === 'creatureProperties'){
|
||||
} else if (parentRef.collection === 'creatureProperties') {
|
||||
rootCreature = getRootCreatureAncestor(parentDoc);
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Add the folder first if we need to
|
||||
if (insertFolderFirst){
|
||||
if (insertFolderFirst) {
|
||||
let order = getHighestOrder({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: parentRef.id,
|
||||
@@ -113,7 +113,7 @@ const insertPropertyAsChildOfTag = new ValidatedMethod({
|
||||
order,
|
||||
});
|
||||
// Make the folder our new parent
|
||||
let newParentRef = {id, collection: 'creatureProperties'};
|
||||
let newParentRef = { id, collection: 'creatureProperties' };
|
||||
ancestors = [parentRef, newParentRef];
|
||||
parentRef = newParentRef;
|
||||
creatureProperty.order = order + 1;
|
||||
@@ -122,14 +122,14 @@ const insertPropertyAsChildOfTag = new ValidatedMethod({
|
||||
creatureProperty.parent = parentRef;
|
||||
creatureProperty.ancestors = ancestors;
|
||||
|
||||
return insertPropertyWork({
|
||||
return insertPropertyWork({
|
||||
property: creatureProperty,
|
||||
creature: rootCreature,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export function insertPropertyWork({property, creature}){
|
||||
export function insertPropertyWork({ property, creature }) {
|
||||
delete property._id;
|
||||
property.dirty = true;
|
||||
let _id = CreatureProperties.insert(property);
|
||||
|
||||
@@ -7,57 +7,57 @@ import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import {
|
||||
setLineageOfDocs,
|
||||
getAncestry,
|
||||
renewDocIds
|
||||
setLineageOfDocs,
|
||||
getAncestry,
|
||||
renewDocIds
|
||||
} from '/imports/api/parenting/parenting.js';
|
||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
|
||||
const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
name: 'creatureProperties.insertPropertyFromLibraryNode',
|
||||
validate: new SimpleSchema({
|
||||
name: 'creatureProperties.insertPropertyFromLibraryNode',
|
||||
validate: new SimpleSchema({
|
||||
nodeIds: {
|
||||
type: Array,
|
||||
max: 20,
|
||||
},
|
||||
'nodeIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
parentRef: {
|
||||
type: RefSchema,
|
||||
},
|
||||
'nodeIds.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
parentRef: {
|
||||
type: RefSchema,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
}).validator(),
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({nodeIds, parentRef, order}) {
|
||||
// get the new ancestry for the properties
|
||||
let {parentDoc, ancestors} = getAncestry({parentRef});
|
||||
run({ nodeIds, parentRef, order }) {
|
||||
// get the new ancestry for the properties
|
||||
let { parentDoc, ancestors } = getAncestry({ parentRef });
|
||||
|
||||
// Check permission to edit
|
||||
// Check permission to edit
|
||||
let rootCreature;
|
||||
if (parentRef.collection === 'creatures'){
|
||||
if (parentRef.collection === 'creatures') {
|
||||
rootCreature = parentDoc;
|
||||
} else if (parentRef.collection === 'creatureProperties'){
|
||||
} else if (parentRef.collection === 'creatureProperties') {
|
||||
rootCreature = getRootCreatureAncestor(parentDoc);
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
} else {
|
||||
throw `${parentRef.collection} is not a valid parent collection`
|
||||
}
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// {libraryId: hasViewPermission}
|
||||
//let libraryPermissionMemoir = {};
|
||||
let node;
|
||||
nodeIds.forEach(nodeId => {
|
||||
nodeIds.forEach(nodeId => {
|
||||
// TODO: Check library view permission for each node before starting
|
||||
node = insertPropertyFromNode(nodeId, ancestors, order);
|
||||
});
|
||||
@@ -70,18 +70,18 @@ const insertPropertyFromLibraryNode = new ValidatedMethod({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: rootCreature._id,
|
||||
});
|
||||
// Return the docId of the last property, the inserted root property
|
||||
return rootId;
|
||||
},
|
||||
// Return the docId of the last property, the inserted root property
|
||||
return rootId;
|
||||
},
|
||||
});
|
||||
|
||||
function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
function insertPropertyFromNode(nodeId, ancestors, order) {
|
||||
// Fetch the library node and its decendents, provided they have not been
|
||||
// removed
|
||||
// TODO: Check permission to read the library this node is in
|
||||
let node = LibraryNodes.findOne({
|
||||
_id: nodeId,
|
||||
removed: {$ne: true},
|
||||
removed: { $ne: true },
|
||||
});
|
||||
if (!node) {
|
||||
if (Meteor.isClient) return;
|
||||
@@ -95,7 +95,7 @@ function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
let oldParent = node.parent;
|
||||
let nodes = LibraryNodes.find({
|
||||
'ancestors.id': nodeId,
|
||||
removed: {$ne: true},
|
||||
removed: { $ne: true },
|
||||
}).fetch();
|
||||
|
||||
// Convert all references into actual nodes
|
||||
@@ -118,11 +118,11 @@ function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
// Give the docs new IDs without breaking internal references
|
||||
renewDocIds({
|
||||
docArray: nodes,
|
||||
collectionMap: {'libraryNodes': 'creatureProperties'}
|
||||
collectionMap: { 'libraryNodes': 'creatureProperties' }
|
||||
});
|
||||
|
||||
// Order the root node
|
||||
if (order === undefined){
|
||||
if (order === undefined) {
|
||||
setDocToLastOrder({
|
||||
collection: CreatureProperties,
|
||||
doc: node,
|
||||
@@ -139,7 +139,7 @@ function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
return node;
|
||||
}
|
||||
|
||||
function storeLibraryNodeReferences(nodes){
|
||||
function storeLibraryNodeReferences(nodes) {
|
||||
nodes.forEach(node => {
|
||||
if (node.libraryNodeId) return;
|
||||
node.libraryNodeId = node._id;
|
||||
@@ -154,7 +154,7 @@ function dirtyNodes(nodes) {
|
||||
|
||||
// Covert node references into actual nodes
|
||||
// TODO: check permissions for each library a reference node references
|
||||
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0) {
|
||||
depth += 1;
|
||||
// New nodes added this function
|
||||
let newNodes = [];
|
||||
@@ -165,9 +165,9 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
if (node.type !== 'reference') return true;
|
||||
|
||||
// We have gone too deep, keep the reference node as an error
|
||||
if (depth >= 10){
|
||||
if (depth >= 10) {
|
||||
if (Meteor.isClient) console.warn('Reference depth limit exceeded');
|
||||
node.cache = {error: 'Reference depth limit exceeded'};
|
||||
node.cache = { error: 'Reference depth limit exceeded' };
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -177,17 +177,17 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
referencedNode.order = node.order;
|
||||
// We are definitely replacing this node, so add it to the list
|
||||
visitedRefs.add(node._id);
|
||||
} catch (e){
|
||||
node.cache = {error: e.reason || e.message || e.toString()};
|
||||
} catch (e) {
|
||||
node.cache = { error: e.reason || e.message || e.toString() };
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get all the descendants of the referenced node
|
||||
let descendents = LibraryNodes.find({
|
||||
'ancestors.id': referencedNode._id,
|
||||
removed: {$ne: true},
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
|
||||
// We are adding the referenced node and its descendants
|
||||
@@ -195,20 +195,20 @@ function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
|
||||
// re-map all the ancestors to parent the new sub-tree into our existing
|
||||
// node tree
|
||||
setLineageOfDocs({
|
||||
docArray: addedNodes,
|
||||
newAncestry: node.ancestors,
|
||||
oldParent: referencedNode.parent,
|
||||
});
|
||||
setLineageOfDocs({
|
||||
docArray: addedNodes,
|
||||
newAncestry: node.ancestors,
|
||||
oldParent: referencedNode.parent,
|
||||
});
|
||||
|
||||
// Filter all the looped references
|
||||
addedNodes = addedNodes.filter(addedNode => {
|
||||
// Add all non-reference nodes
|
||||
if (addedNode.type !== 'reference'){
|
||||
if (addedNode.type !== 'reference') {
|
||||
return true;
|
||||
}
|
||||
// If this exact reference has already been resolved before, filter it out
|
||||
if (visitedRefs.has(addedNode._id)){
|
||||
if (visitedRefs.has(addedNode._id)) {
|
||||
return false;
|
||||
} else {
|
||||
// Otherwise mark it as visited, and keep it
|
||||
|
||||
@@ -5,28 +5,28 @@ import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
|
||||
const pullFromProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.pull',
|
||||
validate: null,
|
||||
name: 'creatureProperties.pull',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, itemId}){
|
||||
run({ _id, path, itemId }) {
|
||||
// Permissions
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Do work
|
||||
CreatureProperties.update(_id, {
|
||||
CreatureProperties.update(_id, {
|
||||
$pull: { [path.join('.')]: { _id: itemId } },
|
||||
$set: { dirty: true }
|
||||
}, {
|
||||
selector: {type: property.type},
|
||||
getAutoValues: false,
|
||||
});
|
||||
}
|
||||
}, {
|
||||
selector: { type: property.type },
|
||||
getAutoValues: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default pullFromProperty;
|
||||
|
||||
@@ -6,16 +6,16 @@ import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/ge
|
||||
import { get } from 'lodash';
|
||||
|
||||
const pushToProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.push',
|
||||
validate: null,
|
||||
name: 'creatureProperties.push',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}){
|
||||
run({ _id, path, value }) {
|
||||
// Permissions
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
@@ -25,10 +25,10 @@ const pushToProperty = new ValidatedMethod({
|
||||
let schema = CreatureProperties.simpleSchema(property);
|
||||
let maxCount = schema.get(joinedPath, 'maxCount');
|
||||
|
||||
if (Number.isFinite(maxCount)){
|
||||
if (Number.isFinite(maxCount)) {
|
||||
let array = get(property, path);
|
||||
let currentCount = array ? array.length : 0;
|
||||
if (currentCount >= maxCount){
|
||||
if (currentCount >= maxCount) {
|
||||
throw new Meteor.Error(
|
||||
'Array is full',
|
||||
`Cannot have more than ${maxCount} values`
|
||||
@@ -37,13 +37,13 @@ const pushToProperty = new ValidatedMethod({
|
||||
}
|
||||
|
||||
// Do work
|
||||
CreatureProperties.update(_id, {
|
||||
CreatureProperties.update(_id, {
|
||||
$push: { [joinedPath]: value },
|
||||
$set: { dirty: true },
|
||||
}, {
|
||||
selector: {type: property.type},
|
||||
});
|
||||
}
|
||||
}, {
|
||||
selector: { type: property.type },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default pushToProperty;
|
||||
|
||||
@@ -7,18 +7,18 @@ import { restore } from '/imports/api/parenting/softRemove.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
|
||||
const restoreProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.restore',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
name: 'creatureProperties.restore',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}){
|
||||
run({ _id }) {
|
||||
// Permissions
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
@@ -30,7 +30,7 @@ const restoreProperty = new ValidatedMethod({
|
||||
$set: { dirty: true }
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default restoreProperty;
|
||||
|
||||
@@ -17,20 +17,20 @@ const selectAmmoItem = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({actionId, itemId, itemConsumedIndex}) {
|
||||
run({ actionId, itemId, itemConsumedIndex }) {
|
||||
// Permissions
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
let rootCreature = getRootCreatureAncestor(action);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Check that this index has a document to edit
|
||||
let itemConsumed = action.resources.itemsConsumed[itemConsumedIndex];
|
||||
if (!itemConsumed){
|
||||
if (!itemConsumed) {
|
||||
throw new Meteor.Error('Resouce not found',
|
||||
'Could not set ammo, because the ammo document was not found');
|
||||
}
|
||||
let itemToLink = CreatureProperties.findOne(itemId);
|
||||
if (!itemToLink){
|
||||
if (!itemToLink) {
|
||||
throw new Meteor.Error('Item not found',
|
||||
'Could not set ammo: the item was not found');
|
||||
}
|
||||
|
||||
@@ -7,24 +7,24 @@ import { softRemove } from '/imports/api/parenting/softRemove.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
|
||||
const softRemoveProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.softRemove',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
name: 'creatureProperties.softRemove',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}){
|
||||
run({ _id }) {
|
||||
// Permissions
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let property = CreatureProperties.findOne(_id);
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
// Do work
|
||||
softRemove({_id, collection: CreatureProperties});
|
||||
}
|
||||
softRemove({ _id, collection: CreatureProperties });
|
||||
}
|
||||
});
|
||||
|
||||
export default softRemoveProperty;
|
||||
|
||||
@@ -6,28 +6,28 @@ import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/ge
|
||||
|
||||
const updateCreatureProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.update',
|
||||
validate({_id, path}){
|
||||
if (!_id) throw new Meteor.Error('No _id', '_id is required');
|
||||
// We cannot change these fields with a simple update
|
||||
switch (path[0]){
|
||||
case 'type':
|
||||
validate({ _id, path }) {
|
||||
if (!_id) throw new Meteor.Error('No _id', '_id is required');
|
||||
// We cannot change these fields with a simple update
|
||||
switch (path[0]) {
|
||||
case 'type':
|
||||
case 'order':
|
||||
case 'parent':
|
||||
case 'ancestors':
|
||||
case 'damage':
|
||||
throw new Meteor.Error('Permission denied',
|
||||
'This property can\'t be updated directly');
|
||||
}
|
||||
case 'damage':
|
||||
throw new Meteor.Error('Permission denied',
|
||||
'This property can\'t be updated directly');
|
||||
}
|
||||
},
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}) {
|
||||
run({ _id, path, value }) {
|
||||
// Permission
|
||||
let property = CreatureProperties.findOne(_id, {
|
||||
fields: {type: 1, ancestors: 1}
|
||||
fields: { type: 1, ancestors: 1 }
|
||||
});
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
@@ -35,14 +35,14 @@ const updateCreatureProperty = new ValidatedMethod({
|
||||
let pathString = path.join('.');
|
||||
let modifier;
|
||||
// unset empty values
|
||||
if (value === null || value === undefined){
|
||||
modifier = { $unset: {[pathString]: 1}, $set: { dirty: true } };
|
||||
if (value === null || value === undefined) {
|
||||
modifier = { $unset: { [pathString]: 1 }, $set: { dirty: true } };
|
||||
} else {
|
||||
modifier = { $set: {[pathString]: value, dirty: true } };
|
||||
modifier = { $set: { [pathString]: value, dirty: true } };
|
||||
}
|
||||
CreatureProperties.update(_id, modifier, {
|
||||
selector: {type: property.type},
|
||||
});
|
||||
CreatureProperties.update(_id, modifier, {
|
||||
selector: { type: property.type },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||
* Recomputes all ancestor creatures of this property
|
||||
*/
|
||||
export default function recomputeCreaturesByProperty(property){
|
||||
for (let ref of property.ancestors){
|
||||
if (ref.collection === 'creatures') {
|
||||
computeCreature.call(ref.id);
|
||||
}
|
||||
}
|
||||
for (let ref of property.ancestors){
|
||||
if (ref.collection === 'creatures') {
|
||||
computeCreature.call(ref.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,21 +8,21 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
let Creatures = new Mongo.Collection('creatures');
|
||||
|
||||
let CreatureSettingsSchema = new SimpleSchema({
|
||||
//slowed down by carrying too much?
|
||||
useVariantEncumbrance: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
//hide spellcasting tab
|
||||
hideSpellcasting: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Swap around the modifier and stat
|
||||
swapStatAndModifier: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
//slowed down by carrying too much?
|
||||
useVariantEncumbrance: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
//hide spellcasting tab
|
||||
hideSpellcasting: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Swap around the modifier and stat
|
||||
swapStatAndModifier: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Hide all the unused stats
|
||||
hideUnusedStats: {
|
||||
type: Boolean,
|
||||
@@ -58,28 +58,28 @@ let CreatureSettingsSchema = new SimpleSchema({
|
||||
});
|
||||
|
||||
let CreatureSchema = new SimpleSchema({
|
||||
// Strings
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: '',
|
||||
optional: true,
|
||||
// Strings
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: '',
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
alignment: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
alignment: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
picture: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
picture: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.url,
|
||||
},
|
||||
},
|
||||
avatarPicture: {
|
||||
type: String,
|
||||
optional: true,
|
||||
@@ -90,37 +90,37 @@ let CreatureSchema = new SimpleSchema({
|
||||
allowedLibraries: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: 100,
|
||||
},
|
||||
'allowedLibraries.$': {
|
||||
type: String,
|
||||
maxCount: 100,
|
||||
},
|
||||
'allowedLibraries.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
allowedLibraryCollections: {
|
||||
type: Array,
|
||||
},
|
||||
allowedLibraryCollections: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: 100,
|
||||
},
|
||||
'allowedLibraryCollections.$': {
|
||||
type: String,
|
||||
maxCount: 100,
|
||||
},
|
||||
'allowedLibraryCollections.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
|
||||
// Mechanics
|
||||
deathSave: {
|
||||
type: deathSaveSchema,
|
||||
defaultValue: {},
|
||||
},
|
||||
// Mechanics
|
||||
deathSave: {
|
||||
type: deathSaveSchema,
|
||||
defaultValue: {},
|
||||
},
|
||||
// Stats that are computed and denormalised outside of recomputation
|
||||
denormalizedStats: {
|
||||
type: Object,
|
||||
defaultValue: {},
|
||||
},
|
||||
// Sum of all XP gained by this character
|
||||
'denormalizedStats.xp': {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 0,
|
||||
},
|
||||
'denormalizedStats.xp': {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 0,
|
||||
},
|
||||
// Sum of all levels granted by milestone XP
|
||||
'denormalizedStats.milestoneLevels': {
|
||||
type: SimpleSchema.Integer,
|
||||
@@ -133,24 +133,24 @@ let CreatureSchema = new SimpleSchema({
|
||||
},
|
||||
// Version of computation engine that was last used to compute this creature
|
||||
computeVersion: {
|
||||
type: String,
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
defaultValue: 'pc',
|
||||
allowedValues: ['pc', 'npc', 'monster'],
|
||||
},
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
defaultValue: 'pc',
|
||||
allowedValues: ['pc', 'npc', 'monster'],
|
||||
},
|
||||
damageMultipliers: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
defaultValue: {}
|
||||
blackbox: true,
|
||||
defaultValue: {}
|
||||
},
|
||||
variables: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
defaultValue: {}
|
||||
},
|
||||
variables: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
defaultValue: {}
|
||||
},
|
||||
computeErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
@@ -161,7 +161,7 @@ let CreatureSchema = new SimpleSchema({
|
||||
'computeErrors.$.type': {
|
||||
type: String,
|
||||
},
|
||||
'computeErrors.$.details' : {
|
||||
'computeErrors.$.details': {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
@@ -178,11 +178,11 @@ let CreatureSchema = new SimpleSchema({
|
||||
optional: true,
|
||||
},
|
||||
|
||||
// Settings
|
||||
settings: {
|
||||
type: CreatureSettingsSchema,
|
||||
defaultValue: {},
|
||||
},
|
||||
// Settings
|
||||
settings: {
|
||||
type: CreatureSettingsSchema,
|
||||
defaultValue: {},
|
||||
},
|
||||
});
|
||||
|
||||
CreatureSchema.extend(ColorSchema);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js';
|
||||
|
||||
@@ -36,8 +36,8 @@ const changeAllowedLibraries = new ValidatedMethod({
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, allowedLibraries, allowedLibraryCollections}) {
|
||||
let creature = Creatures.findOne(_id);
|
||||
run({ _id, allowedLibraries, allowedLibraryCollections }) {
|
||||
let creature = Creatures.findOne(_id);
|
||||
assertEditPermission(creature, this.userId);
|
||||
let $set;
|
||||
if (allowedLibraries) {
|
||||
@@ -48,7 +48,7 @@ const changeAllowedLibraries = new ValidatedMethod({
|
||||
$set.allowedLibraryCollections = allowedLibraryCollections;
|
||||
}
|
||||
if (!$set) return;
|
||||
Creatures.update(_id, {$set});
|
||||
Creatures.update(_id, { $set });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ const toggleAllUserLibraries = new ValidatedMethod({
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, value}) {
|
||||
run({ _id, value }) {
|
||||
if (value) {
|
||||
Creatures.update(_id, {
|
||||
$unset: {
|
||||
@@ -87,4 +87,4 @@ const toggleAllUserLibraries = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
export {changeAllowedLibraries, toggleAllUserLibraries};
|
||||
export { changeAllowedLibraries, toggleAllUserLibraries };
|
||||
|
||||
@@ -51,7 +51,7 @@ const insertCreature = new ValidatedMethod({
|
||||
allowedLibraries,
|
||||
allowedLibraryCollections,
|
||||
});
|
||||
|
||||
|
||||
// Insert experience to get character to starting level
|
||||
if (startingLevel) {
|
||||
insertExperienceForCreature({
|
||||
@@ -70,7 +70,7 @@ const insertCreature = new ValidatedMethod({
|
||||
let baseId, rulesetSlot;
|
||||
defaultCharacterProperties(creatureId).forEach(prop => {
|
||||
let id = CreatureProperties.insert(prop);
|
||||
if (prop.name === 'Ruleset'){
|
||||
if (prop.name === 'Ruleset') {
|
||||
baseId = id;
|
||||
rulesetSlot = prop;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ const insertCreature = new ValidatedMethod({
|
||||
insertDefaultRuleset(creatureId, baseId, userId, rulesetSlot);
|
||||
}
|
||||
|
||||
return creatureId;
|
||||
return creatureId;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -95,7 +95,7 @@ function insertDefaultRuleset(creatureId, baseId, userId, slot) {
|
||||
const ruleset = fillCursor.fetch()[0]
|
||||
insertPropertyFromLibraryNode.call({
|
||||
nodeIds: [ruleset._id],
|
||||
parentRef: {id: baseId, collection: 'creatureProperties'},
|
||||
parentRef: { id: baseId, collection: 'creatureProperties' },
|
||||
order: 0.5,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
|
||||
const updateCreature = new ValidatedMethod({
|
||||
name: 'creatures.update',
|
||||
validate({_id, path}){
|
||||
if (!_id) return false;
|
||||
// Allowed fields
|
||||
let allowedFields = [
|
||||
validate({ _id, path }) {
|
||||
if (!_id) return false;
|
||||
// Allowed fields
|
||||
let allowedFields = [
|
||||
'name',
|
||||
'alignment',
|
||||
'gender',
|
||||
@@ -17,26 +17,26 @@ const updateCreature = new ValidatedMethod({
|
||||
'color',
|
||||
'settings',
|
||||
];
|
||||
if (!allowedFields.includes(path[0])){
|
||||
throw new Meteor.Error('Creatures.methods.update.denied',
|
||||
'This field can\'t be updated using this method');
|
||||
}
|
||||
if (!allowedFields.includes(path[0])) {
|
||||
throw new Meteor.Error('Creatures.methods.update.denied',
|
||||
'This field can\'t be updated using this method');
|
||||
}
|
||||
},
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}) {
|
||||
let creature = Creatures.findOne(_id);
|
||||
run({ _id, path, value }) {
|
||||
let creature = Creatures.findOne(_id);
|
||||
assertEditPermission(creature, this.userId);
|
||||
if (value === undefined || value === null){
|
||||
if (value === undefined || value === null) {
|
||||
Creatures.update(_id, {
|
||||
$unset: {[path.join('.')]: 1},
|
||||
$unset: { [path.join('.')]: 1 },
|
||||
});
|
||||
} else {
|
||||
Creatures.update(_id, {
|
||||
$set: {[path.join('.')]: value},
|
||||
$set: { [path.join('.')]: value },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,17 +8,17 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
let Experiences = new Mongo.Collection('experiences');
|
||||
|
||||
let ExperienceSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The amount of XP this experience gives
|
||||
xp: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
},
|
||||
// The amount of XP this experience gives
|
||||
xp: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
// Setting levels instead of value grants whole levels
|
||||
levels: {
|
||||
type: SimpleSchema.Integer,
|
||||
@@ -26,17 +26,17 @@ let ExperienceSchema = new SimpleSchema({
|
||||
min: 0,
|
||||
index: 1,
|
||||
},
|
||||
// The real-world date that it occured, usually sorted by date
|
||||
date: {
|
||||
type: Date,
|
||||
autoValue: function() {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
// The real-world date that it occured, usually sorted by date
|
||||
date: {
|
||||
type: Date,
|
||||
autoValue: function () {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
@@ -46,8 +46,8 @@ let ExperienceSchema = new SimpleSchema({
|
||||
|
||||
Experiences.attachSchema(ExperienceSchema);
|
||||
|
||||
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
||||
if (experience.xp){
|
||||
const insertExperienceForCreature = function ({ experience, creatureId }) {
|
||||
if (experience.xp) {
|
||||
Creatures.update(creatureId, {
|
||||
$inc: { 'denormalizedStats.xp': experience.xp },
|
||||
$set: { dirty: true },
|
||||
@@ -84,16 +84,16 @@ const insertExperience = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({experience, creatureIds}) {
|
||||
run({ experience, creatureIds }) {
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('Experiences.methods.insert.denied',
|
||||
'You need to be logged in to insert an experience');
|
||||
'You need to be logged in to insert an experience');
|
||||
}
|
||||
let insertedIds = [];
|
||||
creatureIds.forEach(creatureId => {
|
||||
assertEditPermission(creatureId, userId);
|
||||
let id = insertExperienceForCreature({experience, creatureId, userId});
|
||||
let id = insertExperienceForCreature({ experience, creatureId, userId });
|
||||
insertedIds.push(id);
|
||||
});
|
||||
return insertedIds;
|
||||
@@ -113,17 +113,17 @@ const removeExperience = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({experienceId}) {
|
||||
run({ experienceId }) {
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('Experiences.methods.remove.denied',
|
||||
'You need to be logged in to remove an experience');
|
||||
'You need to be logged in to remove an experience');
|
||||
}
|
||||
let experience = Experiences.findOne(experienceId);
|
||||
if (!experience) return;
|
||||
let creatureId = experience.creatureId
|
||||
assertEditPermission(creatureId, userId);
|
||||
if (experience.xp){
|
||||
if (experience.xp) {
|
||||
Creatures.update(creatureId, {
|
||||
$inc: { 'denormalizedStats.xp': -experience.xp },
|
||||
$set: { dirty: true },
|
||||
@@ -154,11 +154,11 @@ const recomputeExperiences = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({creatureId}) {
|
||||
run({ creatureId }) {
|
||||
let userId = this.userId;
|
||||
if (!userId) {
|
||||
throw new Meteor.Error('Experiences.methods.recompute.denied',
|
||||
'You need to be logged in to recompute a creature\'s experiences');
|
||||
'You need to be logged in to recompute a creature\'s experiences');
|
||||
}
|
||||
assertEditPermission(creatureId, userId);
|
||||
|
||||
@@ -167,16 +167,18 @@ const recomputeExperiences = new ValidatedMethod({
|
||||
Experiences.find({
|
||||
creatureId
|
||||
}, {
|
||||
fields: {xp: 1, levels: 1}
|
||||
fields: { xp: 1, levels: 1 }
|
||||
}).forEach(experience => {
|
||||
xp += experience.xp || 0;
|
||||
milestoneLevels += experience.levels || 0;
|
||||
});
|
||||
Creatures.update(creatureId, {$set: {
|
||||
'denormalizedStats.xp': xp,
|
||||
'denormalizedStats.milestoneLevels': milestoneLevels,
|
||||
dirty: true,
|
||||
}});
|
||||
Creatures.update(creatureId, {
|
||||
$set: {
|
||||
'denormalizedStats.xp': xp,
|
||||
'denormalizedStats.milestoneLevels': milestoneLevels,
|
||||
dirty: true,
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,18 +5,18 @@ let ExperienceSchema = new SimpleSchema({
|
||||
title: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// Potentially long description of the event
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// The real-world date that it occured
|
||||
date: {
|
||||
type: Date,
|
||||
autoValue: function() {
|
||||
autoValue: function () {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
@@ -27,24 +27,24 @@ let ExperienceSchema = new SimpleSchema({
|
||||
worldDate: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// Tags to better find this entry later
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
// ID of the journal this entry belongs to
|
||||
journalId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
}
|
||||
// Tags to better find this entry later
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
// ID of the journal this entry belongs to
|
||||
journalId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
}
|
||||
});
|
||||
|
||||
export { ExperienceSchema };
|
||||
|
||||
@@ -4,13 +4,13 @@ import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables
|
||||
import LogContentSchema from '/imports/api/creature/log/LogContentSchema.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import {assertEditPermission} from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import {parse, prettifyParseError} from '/imports/parser/parser.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { parse, prettifyParseError } from '/imports/parser/parser.js';
|
||||
import resolve, { toString } from '/imports/parser/resolve.js';
|
||||
const PER_CREATURE_LOG_LIMIT = 100;
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
if (Meteor.isServer){
|
||||
if (Meteor.isServer) {
|
||||
var sendWebhookAsCreature = require('/imports/server/discord/sendWebhook.js').sendWebhookAsCreature;
|
||||
}
|
||||
|
||||
@@ -25,17 +25,17 @@ let CreatureLogSchema = new SimpleSchema({
|
||||
'content.$': {
|
||||
type: LogContentSchema,
|
||||
},
|
||||
// The real-world date that it occured, usually sorted by date
|
||||
date: {
|
||||
type: Date,
|
||||
autoValue: function() {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
// The real-world date that it occured, usually sorted by date
|
||||
date: {
|
||||
type: Date,
|
||||
autoValue: function () {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
@@ -50,23 +50,23 @@ let CreatureLogSchema = new SimpleSchema({
|
||||
|
||||
CreatureLogs.attachSchema(CreatureLogSchema);
|
||||
|
||||
function removeOldLogs(creatureId){
|
||||
function removeOldLogs(creatureId) {
|
||||
// Find the first log that is over the limit
|
||||
let firstExpiredLog = CreatureLogs.find({
|
||||
creatureId
|
||||
}, {
|
||||
sort: {date: -1},
|
||||
sort: { date: -1 },
|
||||
skip: PER_CREATURE_LOG_LIMIT,
|
||||
});
|
||||
if (!firstExpiredLog) return;
|
||||
// Remove all logs older than the one over the limit
|
||||
CreatureLogs.remove({
|
||||
creatureId,
|
||||
date: {$lte: firstExpiredLog.date},
|
||||
date: { $lte: firstExpiredLog.date },
|
||||
});
|
||||
}
|
||||
|
||||
function logToMessageData(log){
|
||||
function logToMessageData(log) {
|
||||
let embed = {
|
||||
fields: [],
|
||||
};
|
||||
@@ -78,8 +78,8 @@ function logToMessageData(log){
|
||||
return { embeds: [embed] };
|
||||
}
|
||||
|
||||
function logWebhook({log, creature}){
|
||||
if (Meteor.isServer){
|
||||
function logWebhook({ log, creature }) {
|
||||
if (Meteor.isServer) {
|
||||
sendWebhookAsCreature({
|
||||
creature,
|
||||
data: logToMessageData(log),
|
||||
@@ -94,47 +94,49 @@ const insertCreatureLog = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
validate: new SimpleSchema({
|
||||
log: CreatureLogSchema.omit('date'),
|
||||
}).validator(),
|
||||
run({log}){
|
||||
validate: new SimpleSchema({
|
||||
log: CreatureLogSchema.omit('date'),
|
||||
}).validator(),
|
||||
run({ log }) {
|
||||
const creatureId = log.creatureId;
|
||||
const creature = Creatures.findOne(creatureId, {fields: {
|
||||
readers: 1,
|
||||
writers: 1,
|
||||
owner: 1,
|
||||
'settings.discordWebhook': 1,
|
||||
name: 1,
|
||||
avatarPicture: 1,
|
||||
}});
|
||||
const creature = Creatures.findOne(creatureId, {
|
||||
fields: {
|
||||
readers: 1,
|
||||
writers: 1,
|
||||
owner: 1,
|
||||
'settings.discordWebhook': 1,
|
||||
name: 1,
|
||||
avatarPicture: 1,
|
||||
}
|
||||
});
|
||||
assertEditPermission(creature, this.userId);
|
||||
// Build the new log
|
||||
let id = insertCreatureLogWork({log, creature, method: this})
|
||||
let id = insertCreatureLogWork({ log, creature, method: this })
|
||||
return id;
|
||||
},
|
||||
});
|
||||
|
||||
export function insertCreatureLogWork({log, creature, method}){
|
||||
export function insertCreatureLogWork({ log, creature, method }) {
|
||||
// Build the new log
|
||||
if (typeof log === 'string'){
|
||||
log = {content: [{value: log}]};
|
||||
if (typeof log === 'string') {
|
||||
log = { content: [{ value: log }] };
|
||||
}
|
||||
if (!log.content?.length) return;
|
||||
log.date = new Date();
|
||||
// Insert it
|
||||
let id = CreatureLogs.insert(log);
|
||||
if (Meteor.isServer){
|
||||
if (Meteor.isServer) {
|
||||
method?.unblock();
|
||||
removeOldLogs(creature._id);
|
||||
logWebhook({log, creature});
|
||||
logWebhook({ log, creature });
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
function equalIgnoringWhitespace(a, b){
|
||||
function equalIgnoringWhitespace(a, b) {
|
||||
if (typeof a !== 'string' || typeof b !== 'string') return a === b;
|
||||
return a.replace(/\s/g,'') === b.replace(/\s/g, '');
|
||||
return a.replace(/\s/g, '') === b.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
const logRoll = new ValidatedMethod({
|
||||
@@ -144,33 +146,35 @@ const logRoll = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
validate: new SimpleSchema({
|
||||
roll: {
|
||||
type: String,
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
run({roll, creatureId}){
|
||||
const creature = Creatures.findOne(creatureId, {fields: {
|
||||
readers: 1,
|
||||
writers: 1,
|
||||
owner: 1,
|
||||
'settings.discordWebhook': 1,
|
||||
name: 1,
|
||||
avatarPicture: 1,
|
||||
}});
|
||||
validate: new SimpleSchema({
|
||||
roll: {
|
||||
type: String,
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
run({ roll, creatureId }) {
|
||||
const creature = Creatures.findOne(creatureId, {
|
||||
fields: {
|
||||
readers: 1,
|
||||
writers: 1,
|
||||
owner: 1,
|
||||
'settings.discordWebhook': 1,
|
||||
name: 1,
|
||||
avatarPicture: 1,
|
||||
}
|
||||
});
|
||||
assertEditPermission(creature, this.userId);
|
||||
const variables = CreatureVariables.findOne({ _creatureId: creatureId });
|
||||
let logContent = []
|
||||
let parsedResult = undefined;
|
||||
try {
|
||||
parsedResult = parse(roll);
|
||||
} catch (e){
|
||||
} catch (e) {
|
||||
let error = prettifyParseError(e);
|
||||
logContent.push({name: 'Parse Error', value: error});
|
||||
logContent.push({ name: 'Parse Error', value: error });
|
||||
}
|
||||
if (parsedResult) try {
|
||||
let {
|
||||
@@ -184,19 +188,19 @@ const logRoll = new ValidatedMethod({
|
||||
logContent.push({
|
||||
value: compiledString
|
||||
});
|
||||
let {result: rolled} = resolve('roll', compiled, variables, context);
|
||||
let { result: rolled } = resolve('roll', compiled, variables, context);
|
||||
let rolledString = toString(rolled);
|
||||
if (rolledString !== compiledString) logContent.push({
|
||||
value: rolledString
|
||||
});
|
||||
let {result} = resolve('reduce', rolled, variables, context);
|
||||
let { result } = resolve('reduce', rolled, variables, context);
|
||||
let resultString = toString(result);
|
||||
if (resultString !== rolledString) logContent.push({
|
||||
value: resultString
|
||||
});
|
||||
} catch (e){
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logContent = [{name: 'Calculation error'}];
|
||||
logContent = [{ name: 'Calculation error' }];
|
||||
}
|
||||
const log = {
|
||||
content: logContent,
|
||||
@@ -204,11 +208,11 @@ const logRoll = new ValidatedMethod({
|
||||
date: new Date(),
|
||||
};
|
||||
|
||||
let id = insertCreatureLogWork({log, creature, method: this});
|
||||
let id = insertCreatureLogWork({ log, creature, method: this });
|
||||
|
||||
return id;
|
||||
},
|
||||
});
|
||||
|
||||
export default CreatureLogs;
|
||||
export { CreatureLogSchema, insertCreatureLog, logRoll, PER_CREATURE_LOG_LIMIT};
|
||||
export { CreatureLogSchema, insertCreatureLog, logRoll, PER_CREATURE_LOG_LIMIT };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
} 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 computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
|
||||
@@ -15,14 +15,14 @@ import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
|
||||
export default function applyBuff(node, actionContext){
|
||||
export default function applyBuff(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
const prop = node.node;
|
||||
let buffTargets = prop.target === 'self' ? [actionContext.creature] : actionContext.targets;
|
||||
|
||||
// Then copy the decendants of the buff to the targets
|
||||
let propList = [prop];
|
||||
function addChildrenToPropList(children, { skipCrystalize } = {}){
|
||||
function addChildrenToPropList(children, { skipCrystalize } = {}) {
|
||||
children.forEach(child => {
|
||||
if (skipCrystalize) child.node._skipCrystalize = true;
|
||||
propList.push(child.node);
|
||||
@@ -34,7 +34,7 @@ export default function applyBuff(node, actionContext){
|
||||
}
|
||||
addChildrenToPropList(node.children);
|
||||
if (!prop.skipCrystalization) {
|
||||
crystalizeVariables({propList, actionContext});
|
||||
crystalizeVariables({ propList, actionContext });
|
||||
}
|
||||
|
||||
let oldParent = {
|
||||
@@ -46,8 +46,8 @@ export default function applyBuff(node, actionContext){
|
||||
copyNodeListToTarget(propList, target, oldParent);
|
||||
|
||||
//Log the buff
|
||||
if ((prop.name || prop.description?.value) && !prop.silent){
|
||||
if (target._id === actionContext.creature._id){
|
||||
if ((prop.name || prop.description?.value) && !prop.silent) {
|
||||
if (target._id === actionContext.creature._id) {
|
||||
// Targeting self
|
||||
actionContext.addLog({
|
||||
name: prop.name,
|
||||
@@ -72,8 +72,8 @@ export default function applyBuff(node, actionContext){
|
||||
// Don't apply the children of the buff, they get copied to the target instead
|
||||
}
|
||||
|
||||
function copyNodeListToTarget(propList, target, oldParent){
|
||||
let ancestry = [{collection: 'creatures', id: target._id}];
|
||||
function copyNodeListToTarget(propList, target, oldParent) {
|
||||
let ancestry = [{ collection: 'creatures', id: target._id }];
|
||||
setLineageOfDocs({
|
||||
docArray: propList,
|
||||
newAncestry: ancestry,
|
||||
@@ -93,14 +93,14 @@ function copyNodeListToTarget(propList, target, oldParent){
|
||||
* Replaces all variables with their resolved values
|
||||
* except variables of the form `$target.thing.total` become `thing.total`
|
||||
*/
|
||||
function crystalizeVariables({propList, actionContext}){
|
||||
function crystalizeVariables({ propList, actionContext }) {
|
||||
propList.forEach(prop => {
|
||||
if (prop._skipCrystalize) {
|
||||
delete prop._skipCrystalize;
|
||||
return;
|
||||
}
|
||||
// Iterate through all the calculations and crystalize them
|
||||
computedSchemas[prop.type].computedFields().forEach( calcKey => {
|
||||
computedSchemas[prop.type].computedFields().forEach(calcKey => {
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj?.parseNode) return;
|
||||
@@ -110,12 +110,12 @@ function crystalizeVariables({propList, actionContext}){
|
||||
node.parseType !== 'accessor' && node.parseType !== 'symbol'
|
||||
) return node;
|
||||
// Handle variables
|
||||
if (node.name === '$target'){
|
||||
if (node.name === '$target') {
|
||||
// strip $target
|
||||
if (node.parseType === 'accessor'){
|
||||
if (node.parseType === 'accessor') {
|
||||
node.name = node.path.shift();
|
||||
if (!node.path.length){
|
||||
return symbol.create({name: node.name})
|
||||
if (!node.path.length) {
|
||||
return symbol.create({ name: node.name })
|
||||
}
|
||||
} else {
|
||||
// Can't strip symbols
|
||||
@@ -127,7 +127,7 @@ function crystalizeVariables({propList, actionContext}){
|
||||
return node;
|
||||
} else {
|
||||
// Resolve all other variables
|
||||
const {result, context} = resolve('reduce', node, actionContext.scope);
|
||||
const { result, context } = resolve('reduce', node, actionContext.scope);
|
||||
logErrors(context.errors, actionContext);
|
||||
return result;
|
||||
}
|
||||
@@ -137,14 +137,14 @@ function crystalizeVariables({propList, actionContext}){
|
||||
});
|
||||
});
|
||||
// For each key in the schema
|
||||
computedSchemas[prop.type].inlineCalculationFields().forEach( calcKey => {
|
||||
computedSchemas[prop.type].inlineCalculationFields().forEach(calcKey => {
|
||||
// That ends in .inlineCalculations
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const inlineCalcObj = get(prop, key);
|
||||
if (!inlineCalcObj) return;
|
||||
|
||||
// If there is no text, skip
|
||||
if (!inlineCalcObj.text){
|
||||
if (!inlineCalcObj.text) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ const doAction = new ValidatedMethod({
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
const creatureId = action.ancestors[0].id;
|
||||
const actionContext = new ActionContext(creatureId, targetIds, this);
|
||||
|
||||
// Check permissions
|
||||
|
||||
// Check permissions
|
||||
assertEditPermission(actionContext.creature, this.userId);
|
||||
actionContext.targets.forEach(target => {
|
||||
assertEditPermission(target, this.userId);
|
||||
@@ -56,13 +56,13 @@ const doAction = new ValidatedMethod({
|
||||
properties.sort((a, b) => a.order - b.order);
|
||||
|
||||
// Do the action
|
||||
doActionWork({properties, ancestors, actionContext, methodScope: scope});
|
||||
doActionWork({ properties, ancestors, actionContext, methodScope: scope });
|
||||
|
||||
// Recompute all involved creatures
|
||||
Creatures.update({
|
||||
_id: { $in: [creatureId, ...targetIds] }
|
||||
}, {
|
||||
$set: {dirty: true},
|
||||
$set: { dirty: true },
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -71,11 +71,11 @@ export default doAction;
|
||||
|
||||
export function doActionWork({
|
||||
properties, ancestors, actionContext, methodScope = {},
|
||||
}){
|
||||
}) {
|
||||
// get the docs
|
||||
const ancestorScope = getAncestorScope(ancestors);
|
||||
const propertyForest = nodeArrayToTree(properties);
|
||||
if (propertyForest.length !== 1){
|
||||
if (propertyForest.length !== 1) {
|
||||
throw new Meteor.Error(`The action has ${propertyForest.length} top level properties, expected 1`);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export function doActionWork({
|
||||
}
|
||||
|
||||
// Assumes ancestors are in tree order already
|
||||
function getAncestorScope(ancestors){
|
||||
function getAncestorScope(ancestors) {
|
||||
let scope = {};
|
||||
ancestors.forEach(prop => {
|
||||
scope[`#${prop.type}`] = prop;
|
||||
|
||||
@@ -42,12 +42,12 @@ const doAction = new ValidatedMethod({
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ spellId, slotId, targetIds = [], scope = {} }) {
|
||||
// Get action context
|
||||
// Get action context
|
||||
let spell = CreatureProperties.findOne(spellId);
|
||||
const creatureId = spell.ancestors[0].id;
|
||||
const actionContext = new ActionContext(creatureId, targetIds, this);
|
||||
|
||||
// Check permissions
|
||||
// Check permissions
|
||||
assertEditPermission(actionContext.creature, this.userId);
|
||||
actionContext.targets.forEach(target => {
|
||||
assertEditPermission(target, this.userId);
|
||||
@@ -64,25 +64,25 @@ const doAction = new ValidatedMethod({
|
||||
let slotLevel = spell.level || 0;
|
||||
let slot;
|
||||
|
||||
if (slotId && !spell.castWithoutSpellSlots){
|
||||
if (slotId && !spell.castWithoutSpellSlots) {
|
||||
slot = CreatureProperties.findOne(slotId);
|
||||
if (!slot){
|
||||
if (!slot) {
|
||||
throw new Meteor.Error('No slot',
|
||||
'Slot not found to cast spell');
|
||||
}
|
||||
if (!slot.value){
|
||||
if (!slot.value) {
|
||||
throw new Meteor.Error('No slot',
|
||||
'Slot depleted');
|
||||
}
|
||||
if (slot.attributeType !== 'spellSlot'){
|
||||
if (slot.attributeType !== 'spellSlot') {
|
||||
throw new Meteor.Error('Not a slot',
|
||||
'The given property is not a valid spell slot');
|
||||
}
|
||||
if (!slot.spellSlotLevel?.value){
|
||||
if (!slot.spellSlotLevel?.value) {
|
||||
throw new Meteor.Error('No slot level',
|
||||
'Slot does not have a spell slot level');
|
||||
}
|
||||
if (slot.spellSlotLevel.value < spell.level){
|
||||
if (slot.spellSlotLevel.value < spell.level) {
|
||||
throw new Meteor.Error('Slot too small',
|
||||
'Slot is not large enough to cast spell');
|
||||
}
|
||||
@@ -96,7 +96,7 @@ const doAction = new ValidatedMethod({
|
||||
}
|
||||
|
||||
// Post the slot level spent to the log
|
||||
if (slot?.spellSlotLevel?.value){
|
||||
if (slot?.spellSlotLevel?.value) {
|
||||
actionContext.addLog({
|
||||
name: `Casting using a level ${slotLevel} spell slot`
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { pick } from "lodash";
|
||||
import { pick } from 'lodash';
|
||||
|
||||
export default function aggregateEffect({node, linkedNode, link}){
|
||||
if (link.data !== 'effect') return;
|
||||
|
||||
@@ -7,17 +7,17 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
let Icons = new Mongo.Collection('icons');
|
||||
|
||||
let iconsSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
name: {
|
||||
type: String,
|
||||
unique: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
index: 1,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
@@ -38,7 +38,7 @@ if (Meteor.isServer) {
|
||||
Icons._ensureIndex({
|
||||
'name': 'text',
|
||||
'description': 'text',
|
||||
'tags': 'text',
|
||||
'tags': 'text',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,15 +55,15 @@ Icons.attachSchema(iconsSchema);
|
||||
|
||||
// This method does not validate icons against the schema, use wisely;
|
||||
const writeIcons = new ValidatedMethod({
|
||||
name: 'icons.write',
|
||||
validate: null,
|
||||
run(icons){
|
||||
name: 'icons.write',
|
||||
validate: null,
|
||||
run(icons) {
|
||||
assertAdmin(this.userId);
|
||||
if (Meteor.isServer){
|
||||
if (Meteor.isServer) {
|
||||
this.unblock();
|
||||
Icons.rawCollection().insert(icons, {ordered: false});
|
||||
Icons.rawCollection().insert(icons, { ordered: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const findIcons = new ValidatedMethod({
|
||||
@@ -80,11 +80,11 @@ const findIcons = new ValidatedMethod({
|
||||
numRequests: 20,
|
||||
timeInterval: 10000,
|
||||
},
|
||||
run({search}){
|
||||
run({ search }) {
|
||||
if (!search) return [];
|
||||
if (!Meteor.isServer) return;
|
||||
return Icons.find(
|
||||
{ $text: {$search: search} },
|
||||
{ $text: { $search: search } },
|
||||
{
|
||||
// relevant documents have a higher score.
|
||||
fields: {
|
||||
|
||||
@@ -21,14 +21,14 @@ let Libraries = new Mongo.Collection('libraries');
|
||||
|
||||
let LibrarySchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
});
|
||||
|
||||
LibrarySchema.extend(SharingSchema);
|
||||
@@ -38,24 +38,24 @@ Libraries.attachSchema(LibrarySchema);
|
||||
export default Libraries;
|
||||
|
||||
const insertLibrary = new ValidatedMethod({
|
||||
name: 'libraries.insert',
|
||||
name: 'libraries.insert',
|
||||
mixins: [
|
||||
simpleSchemaMixin,
|
||||
],
|
||||
schema: LibrarySchema.omit('owner'),
|
||||
run(library) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
'You need to be logged in to insert a library');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits){
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
`The ${tier.name} tier does not allow you to insert a library`);
|
||||
}
|
||||
library.owner = this.userId;
|
||||
],
|
||||
schema: LibrarySchema.omit('owner'),
|
||||
run(library) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
'You need to be logged in to insert a library');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
`The ${tier.name} tier does not allow you to insert a library`);
|
||||
}
|
||||
library.owner = this.userId;
|
||||
return Libraries.insert(library);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const updateLibraryName = new ValidatedMethod({
|
||||
@@ -69,15 +69,15 @@ const updateLibraryName = new ValidatedMethod({
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, name}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, name }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, {$set: {name}});
|
||||
Libraries.update(_id, { $set: { name } });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -92,15 +92,15 @@ const updateLibraryDescription = new ValidatedMethod({
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, description}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, description }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, {$set: {description}});
|
||||
Libraries.update(_id, { $set: { description } });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -112,22 +112,22 @@ const removeLibrary = new ValidatedMethod({
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertOwnership(library, this.userId);
|
||||
this.unblock();
|
||||
removeLibaryWork(_id)
|
||||
this.unblock();
|
||||
removeLibaryWork(_id)
|
||||
}
|
||||
});
|
||||
|
||||
export function removeLibaryWork(libraryId){
|
||||
Libraries.remove(libraryId);
|
||||
LibraryNodes.remove({'ancestors.id': libraryId});
|
||||
export function removeLibaryWork(libraryId) {
|
||||
Libraries.remove(libraryId);
|
||||
LibraryNodes.remove({ 'ancestors.id': libraryId });
|
||||
}
|
||||
|
||||
export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, removeLibrary };
|
||||
|
||||
@@ -23,28 +23,28 @@ let LibraryNodeSchema = new SimpleSchema({
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
type: {
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: Object.keys(propertySchemasIndex),
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
},
|
||||
libraryTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'libraryTags.$': {
|
||||
type: String,
|
||||
},
|
||||
'libraryTags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
type: storedIconsSchema,
|
||||
optional: true,
|
||||
@@ -56,37 +56,37 @@ let LibraryNodeSchema = new SimpleSchema({
|
||||
if (Meteor.isServer) {
|
||||
LibraryNodes._ensureIndex({
|
||||
'name': 'text',
|
||||
'tags': 'text',
|
||||
'tags': 'text',
|
||||
});
|
||||
}
|
||||
|
||||
for (let key in propertySchemasIndex){
|
||||
let schema = new SimpleSchema({});
|
||||
schema.extend(LibraryNodeSchema);
|
||||
for (let key in propertySchemasIndex) {
|
||||
let schema = new SimpleSchema({});
|
||||
schema.extend(LibraryNodeSchema);
|
||||
schema.extend(ColorSchema);
|
||||
schema.extend(propertySchemasIndex[key]);
|
||||
schema.extend(ChildSchema);
|
||||
schema.extend(SoftRemovableSchema);
|
||||
LibraryNodes.attachSchema(schema, {
|
||||
selector: {type: key}
|
||||
});
|
||||
schema.extend(propertySchemasIndex[key]);
|
||||
schema.extend(ChildSchema);
|
||||
schema.extend(SoftRemovableSchema);
|
||||
LibraryNodes.attachSchema(schema, {
|
||||
selector: { type: key }
|
||||
});
|
||||
}
|
||||
|
||||
function getLibrary(node){
|
||||
function getLibrary(node) {
|
||||
if (!node) throw new Meteor.Error('No node provided');
|
||||
let library = Libraries.findOne(node.ancestors[0].id);
|
||||
if (!library) throw new Meteor.Error('Library does not exist');
|
||||
return library;
|
||||
}
|
||||
|
||||
function assertNodeEditPermission(node, userId){
|
||||
function assertNodeEditPermission(node, userId) {
|
||||
let lib = getLibrary(node);
|
||||
return assertEditPermission(lib, userId);
|
||||
}
|
||||
|
||||
const insertNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.insert',
|
||||
validate: null,
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
@@ -95,8 +95,8 @@ const insertNode = new ValidatedMethod({
|
||||
run(libraryNode) {
|
||||
delete libraryNode._id;
|
||||
assertNodeEditPermission(libraryNode, this.userId);
|
||||
let nodeId = LibraryNodes.insert(libraryNode);
|
||||
if (libraryNode.type == 'reference'){
|
||||
let nodeId = LibraryNodes.insert(libraryNode);
|
||||
if (libraryNode.type == 'reference') {
|
||||
libraryNode._id = nodeId;
|
||||
updateReferenceNodeWork(libraryNode, this.userId);
|
||||
}
|
||||
@@ -106,37 +106,37 @@ const insertNode = new ValidatedMethod({
|
||||
|
||||
const updateLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.update',
|
||||
validate({_id, path}){
|
||||
if (!_id) return false;
|
||||
// We cannot change these fields with a simple update
|
||||
switch (path[0]){
|
||||
case 'type':
|
||||
validate({ _id, path }) {
|
||||
if (!_id) return false;
|
||||
// We cannot change these fields with a simple update
|
||||
switch (path[0]) {
|
||||
case 'type':
|
||||
case 'order':
|
||||
case 'parent':
|
||||
case 'ancestors':
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}) {
|
||||
run({ _id, path, value }) {
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
let pathString = path.join('.');
|
||||
let modifier;
|
||||
// unset empty values
|
||||
if (value === null || value === undefined){
|
||||
modifier = {$unset: {[pathString]: 1}};
|
||||
if (value === null || value === undefined) {
|
||||
modifier = { $unset: { [pathString]: 1 } };
|
||||
} else {
|
||||
modifier = {$set: {[pathString]: value}};
|
||||
modifier = { $set: { [pathString]: value } };
|
||||
}
|
||||
let numUpdated = LibraryNodes.update(_id, modifier, {
|
||||
selector: {type: node.type},
|
||||
});
|
||||
if (node.type == 'reference'){
|
||||
let numUpdated = LibraryNodes.update(_id, modifier, {
|
||||
selector: { type: node.type },
|
||||
});
|
||||
if (node.type == 'reference') {
|
||||
node = LibraryNodes.findOne(_id);
|
||||
updateReferenceNodeWork(node, this.userId);
|
||||
}
|
||||
@@ -145,87 +145,87 @@ const updateLibraryNode = new ValidatedMethod({
|
||||
});
|
||||
|
||||
const pushToLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.push',
|
||||
validate: null,
|
||||
name: 'libraryNodes.push',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, value}){
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
run({ _id, path, value }) {
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
return LibraryNodes.update(_id, {
|
||||
$push: {[path.join('.')]: value},
|
||||
}, {
|
||||
selector: {type: node.type},
|
||||
});
|
||||
}
|
||||
return LibraryNodes.update(_id, {
|
||||
$push: { [path.join('.')]: value },
|
||||
}, {
|
||||
selector: { type: node.type },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const pullFromLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.pull',
|
||||
validate: null,
|
||||
name: 'libraryNodes.pull',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, path, itemId}){
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
run({ _id, path, itemId }) {
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
return LibraryNodes.update(_id, {
|
||||
$pull: {[path.join('.')]: {_id: itemId}},
|
||||
}, {
|
||||
selector: {type: node.type},
|
||||
getAutoValues: false,
|
||||
});
|
||||
}
|
||||
return LibraryNodes.update(_id, {
|
||||
$pull: { [path.join('.')]: { _id: itemId } },
|
||||
}, {
|
||||
selector: { type: node.type },
|
||||
getAutoValues: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const softRemoveLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.softRemove',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
name: 'libraryNodes.softRemove',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}){
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
run({ _id }) {
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
softRemove({_id, collection: LibraryNodes});
|
||||
}
|
||||
softRemove({ _id, collection: LibraryNodes });
|
||||
}
|
||||
});
|
||||
|
||||
const restoreLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.restore',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
name: 'libraryNodes.restore',
|
||||
validate: new SimpleSchema({
|
||||
_id: SimpleSchema.RegEx.Id
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}){
|
||||
run({ _id }) {
|
||||
// Permissions
|
||||
let node = LibraryNodes.findOne(_id);
|
||||
assertNodeEditPermission(node, this.userId);
|
||||
// Do work
|
||||
restore({_id, collection: LibraryNodes});
|
||||
}
|
||||
restore({ _id, collection: LibraryNodes });
|
||||
}
|
||||
});
|
||||
|
||||
export default LibraryNodes;
|
||||
export {
|
||||
LibraryNodeSchema,
|
||||
insertNode,
|
||||
updateLibraryNode,
|
||||
pullFromLibraryNode,
|
||||
pushToLibraryNode,
|
||||
softRemoveLibraryNode,
|
||||
LibraryNodeSchema,
|
||||
insertNode,
|
||||
updateLibraryNode,
|
||||
pullFromLibraryNode,
|
||||
pushToLibraryNode,
|
||||
softRemoveLibraryNode,
|
||||
restoreLibraryNode,
|
||||
};
|
||||
|
||||
@@ -4,13 +4,13 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import {
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
setLineageOfDocs,
|
||||
renewDocIds
|
||||
} from '/imports/api/parenting/parenting.js';
|
||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||
|
||||
var snackbar;
|
||||
if (Meteor.isClient){
|
||||
if (Meteor.isClient) {
|
||||
snackbar = require(
|
||||
'/imports/ui/components/snackbars/SnackbarQueue.js'
|
||||
).snackbar
|
||||
@@ -20,7 +20,7 @@ const DUPLICATE_CHILDREN_LIMIT = 50;
|
||||
|
||||
const duplicateLibraryNode = new ValidatedMethod({
|
||||
name: 'libraryNodes.duplicate',
|
||||
validate: new SimpleSchema({
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
@@ -31,7 +31,7 @@ const duplicateLibraryNode = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id}) {
|
||||
run({ _id }) {
|
||||
let libraryNode = LibraryNodes.findOne(_id);
|
||||
assertDocEditPermission(libraryNode, this.userId);
|
||||
|
||||
@@ -40,16 +40,16 @@ const duplicateLibraryNode = new ValidatedMethod({
|
||||
libraryNode._id = libraryNodeId;
|
||||
|
||||
let nodes = LibraryNodes.find({
|
||||
'ancestors.id': _id,
|
||||
removed: {$ne: true},
|
||||
}, {
|
||||
'ancestors.id': _id,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
limit: DUPLICATE_CHILDREN_LIMIT + 1,
|
||||
sort: {order: 1},
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
|
||||
if (nodes.length > DUPLICATE_CHILDREN_LIMIT){
|
||||
if (nodes.length > DUPLICATE_CHILDREN_LIMIT) {
|
||||
nodes.pop();
|
||||
if (Meteor.isClient){
|
||||
if (Meteor.isClient) {
|
||||
snackbar({
|
||||
text: `Only the first ${DUPLICATE_CHILDREN_LIMIT} children were duplicated`,
|
||||
});
|
||||
@@ -58,21 +58,21 @@ const duplicateLibraryNode = new ValidatedMethod({
|
||||
|
||||
// re-map all the ancestors
|
||||
setLineageOfDocs({
|
||||
docArray: nodes,
|
||||
newAncestry : [
|
||||
docArray: nodes,
|
||||
newAncestry: [
|
||||
...libraryNode.ancestors,
|
||||
{id: libraryNodeId, collection: 'libraryNodes'}
|
||||
{ id: libraryNodeId, collection: 'libraryNodes' }
|
||||
],
|
||||
oldParent : {id: _id, collection: 'libraryNodes'},
|
||||
});
|
||||
oldParent: { id: _id, collection: 'libraryNodes' },
|
||||
});
|
||||
|
||||
// Give the docs new IDs without breaking internal references
|
||||
renewDocIds({docArray: nodes});
|
||||
renewDocIds({ docArray: nodes });
|
||||
|
||||
// Order the root node
|
||||
libraryNode.order += 0.5;
|
||||
|
||||
LibraryNodes.batchInsert([libraryNode, ...nodes]);
|
||||
LibraryNodes.batchInsert([libraryNode, ...nodes]);
|
||||
|
||||
// Tree structure changed by inserts, reorder the tree
|
||||
reorderDocs({
|
||||
|
||||
@@ -22,7 +22,7 @@ let ChildSchema = new SimpleSchema({
|
||||
order: {
|
||||
type: Number,
|
||||
},
|
||||
parent: {
|
||||
parent: {
|
||||
type: RefSchema,
|
||||
optional: true,
|
||||
},
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
let SoftRemovableSchema = new SimpleSchema({
|
||||
"removed": {
|
||||
'removed': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
"removedAt": {
|
||||
'removedAt': {
|
||||
type: Date,
|
||||
optional: true,
|
||||
index: 1,
|
||||
},
|
||||
"removedWith": {
|
||||
'removedWith': {
|
||||
optional: true,
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
|
||||
@@ -3,7 +3,7 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
const AdjustmentSchema = createPropertySchema({
|
||||
// The roll that determines how much to change the attribute
|
||||
// The roll that determines how much to change the attribute
|
||||
// This can be simplified, but should only compute when activated
|
||||
amount: {
|
||||
type: 'fieldToCompute',
|
||||
@@ -11,21 +11,21 @@ const AdjustmentSchema = createPropertySchema({
|
||||
optional: true,
|
||||
defaultValue: 1,
|
||||
},
|
||||
// Who this adjustment applies to
|
||||
target: {
|
||||
type: String,
|
||||
// Who this adjustment applies to
|
||||
target: {
|
||||
type: String,
|
||||
defaultValue: 'target',
|
||||
allowedValues: [
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
},
|
||||
// The stat this rolls applies to
|
||||
stat: {
|
||||
type: String,
|
||||
},
|
||||
// The stat this rolls applies to
|
||||
stat: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
},
|
||||
operation: {
|
||||
type: String,
|
||||
allowedValues: ['set', 'increment'],
|
||||
|
||||
@@ -8,34 +8,34 @@ import createPropertySchema from '/imports/api/properties/subSchemas/createPrope
|
||||
*/
|
||||
let AttributeSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
type: String,
|
||||
optional: true,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// How it is displayed and computed is determined by type
|
||||
// How it is displayed and computed is determined by type
|
||||
attributeType: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'ability', //Strength, Dex, Con, etc.
|
||||
'stat', // Speed, Armor Class
|
||||
'modifier', // Proficiency Bonus, displayed as +x
|
||||
'modifier', // Proficiency Bonus, displayed as +x
|
||||
'hitDice', // d12 hit dice
|
||||
'healthBar', // Hitpoints, Temporary Hitpoints, can take damage
|
||||
'bar', // Displayed as a health bar, can't take damage
|
||||
'bar', // Displayed as a health bar, can't take damage
|
||||
'resource', // Rages, sorcery points
|
||||
'spellSlot', // Level 1, 2, 3... spell slots
|
||||
'utility', // Aren't displayed, Jump height, Carry capacity
|
||||
],
|
||||
defaultValue: 'stat',
|
||||
index: 1,
|
||||
index: 1,
|
||||
},
|
||||
// For type hitDice, the size needs to be stored separately
|
||||
hitDiceSize: {
|
||||
@@ -46,19 +46,19 @@ let AttributeSchema = createPropertySchema({
|
||||
// For type spellSlot, the level needs to be stored separately
|
||||
spellSlotLevel: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
// For type healthBar midColor, and lowColor can be set separately from the
|
||||
// property's color, which is used as the undamaged color
|
||||
'healthBarColorMid': {
|
||||
type: String,
|
||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||
optional: true,
|
||||
type: String,
|
||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||
optional: true,
|
||||
},
|
||||
'healthBarColorLow': {
|
||||
type: String,
|
||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||
optional: true,
|
||||
type: String,
|
||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||
optional: true,
|
||||
},
|
||||
// Control how the health bar takes damage or healing
|
||||
healthBarNoDamage: {
|
||||
@@ -68,7 +68,7 @@ let AttributeSchema = createPropertySchema({
|
||||
healthBarNoHealing: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
healthBarDamageOrder: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
@@ -77,17 +77,17 @@ let AttributeSchema = createPropertySchema({
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
},
|
||||
// The starting value, before effects
|
||||
baseValue: {
|
||||
// The starting value, before effects
|
||||
baseValue: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
optional: true,
|
||||
},
|
||||
// Description of what the attribute is used for
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// The damage done to the attribute, should always compute as positive
|
||||
optional: true,
|
||||
},
|
||||
// The damage done to the attribute, should always compute as positive
|
||||
damage: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
@@ -107,7 +107,7 @@ let AttributeSchema = createPropertySchema({
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Automatically zero the adjustment on these conditions
|
||||
// Automatically zero the adjustment on these conditions
|
||||
reset: {
|
||||
type: String,
|
||||
optional: true,
|
||||
@@ -126,9 +126,9 @@ let ComputedOnlyAttributeSchema = createPropertySchema({
|
||||
},
|
||||
spellSlotLevel: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
// The computed value of the attribute
|
||||
// The computed value of the attribute
|
||||
total: {
|
||||
type: SimpleSchema.oneOf(Number, String, Boolean),
|
||||
optional: true,
|
||||
@@ -137,27 +137,27 @@ let ComputedOnlyAttributeSchema = createPropertySchema({
|
||||
// The computed value of the attribute minus the damage
|
||||
value: {
|
||||
type: SimpleSchema.oneOf(Number, String, Boolean),
|
||||
defaultValue: 0,
|
||||
defaultValue: 0,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// The computed modifier, provided the attribute type is `ability`
|
||||
modifier: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
// The computed modifier, provided the attribute type is `ability`
|
||||
modifier: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
},
|
||||
// Attributes with proficiency grant it to all skills based on the attribute
|
||||
proficiency: {
|
||||
type: Number,
|
||||
type: Number,
|
||||
allowedValues: [0, 0.49, 0.5, 1, 2],
|
||||
optional: true,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
},
|
||||
// The computed creature constitution modifier for hit dice
|
||||
constitutionMod: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Should this attribute hide
|
||||
|
||||
@@ -3,8 +3,8 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let BranchSchema = createPropertySchema({
|
||||
branchType: {
|
||||
type: String,
|
||||
branchType: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
// Uses the condition field to determine whether to apply children
|
||||
'if',
|
||||
@@ -26,7 +26,7 @@ let BranchSchema = createPropertySchema({
|
||||
//'option',
|
||||
],
|
||||
defaultValue: 'if',
|
||||
},
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
optional: true,
|
||||
|
||||
@@ -4,31 +4,31 @@ import createPropertySchema from '/imports/api/properties/subSchemas/createPrope
|
||||
|
||||
let BuffRemoverSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// This will remove just the nearest ancestor buff
|
||||
targetParentBuff: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// The following only applies when not targeting the parent buff
|
||||
// Which character to remove buffs from
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
},
|
||||
// This will remove just the nearest ancestor buff
|
||||
targetParentBuff: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// The following only applies when not targeting the parent buff
|
||||
// Which character to remove buffs from
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// remove 1 or remove all
|
||||
removeAll: {
|
||||
type: Boolean,
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// remove 1 or remove all
|
||||
removeAll: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
},
|
||||
},
|
||||
// Buffs to remove based on tags:
|
||||
targetTags: {
|
||||
type: Array,
|
||||
@@ -50,7 +50,7 @@ let BuffRemoverSchema = createPropertySchema({
|
||||
'extraTags.$._id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
autoValue() {
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,10 +3,10 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let BuffSchema = createPropertySchema({
|
||||
name: {
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
@@ -16,36 +16,36 @@ let BuffSchema = createPropertySchema({
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// How many rounds this buff lasts
|
||||
// How many rounds this buff lasts
|
||||
duration: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
target: {
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Prevent the children from being crystalized
|
||||
skipCrystalization: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
skipCrystalization: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyBuffSchema = createPropertySchema({
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
duration: {
|
||||
type: 'computedOnlyField',
|
||||
@@ -58,11 +58,11 @@ let ComputedOnlyBuffSchema = createPropertySchema({
|
||||
},
|
||||
appliedBy: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
'appliedBy.name': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
'appliedBy.id': {
|
||||
type: String,
|
||||
@@ -70,12 +70,12 @@ let ComputedOnlyBuffSchema = createPropertySchema({
|
||||
},
|
||||
'appliedBy.collection': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedBuffSchema = new SimpleSchema()
|
||||
.extend(BuffSchema)
|
||||
.extend(ComputedOnlyBuffSchema);
|
||||
.extend(BuffSchema)
|
||||
.extend(ComputedOnlyBuffSchema);
|
||||
|
||||
export { BuffSchema, ComputedOnlyBuffSchema, ComputedBuffSchema };
|
||||
|
||||
@@ -4,26 +4,26 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
const ClassLevelSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// The name of this class level's variable
|
||||
variableName: {
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// The name of this class level's variable
|
||||
variableName: {
|
||||
type: String,
|
||||
min: 2,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
optional: true,
|
||||
},
|
||||
level: {
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
defaultValue: 1,
|
||||
max: STORAGE_LIMITS.levelMax,
|
||||
},
|
||||
// Filters out of UI if condition isn't met, but isn't otherwise enforced
|
||||
@@ -34,7 +34,7 @@ const ClassLevelSchema = createPropertySchema({
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyClassLevelSchema = createPropertySchema({
|
||||
const ComputedOnlyClassLevelSchema = createPropertySchema({
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
|
||||
@@ -13,29 +13,29 @@ import resolve, { Context, traverse } from '/imports/parser/resolve.js';
|
||||
*/
|
||||
let ConstantSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
type: String,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
defaultValue: 'newConstant',
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// The input value to be parsed, must return a constant node or an array
|
||||
// The input value to be parsed, must return a constant node or an array
|
||||
// of constant nodes to be valid
|
||||
calculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
calculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
},
|
||||
errors: {
|
||||
type: Array,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
autoValue(){
|
||||
autoValue() {
|
||||
let calc = this.field('calculation');
|
||||
if (!calc.isSet && this.isModifier) {
|
||||
this.unset()
|
||||
@@ -44,27 +44,27 @@ let ConstantSchema = new SimpleSchema({
|
||||
let string = calc.value;
|
||||
if (!string) return [];
|
||||
// Evaluate the calculation with no scope
|
||||
let {result, context} = parseString(string);
|
||||
let { result, context } = parseString(string);
|
||||
// Any existing errors will result in an early failure
|
||||
if (context && context.errors.length) return context.errors;
|
||||
// Ban variables in constants if necessary
|
||||
result && traverse(result, node => {
|
||||
if (node.parseType === 'symbol' || node.parseType === 'accessor'){
|
||||
if (node.parseType === 'symbol' || node.parseType === 'accessor') {
|
||||
context.error('Variables can\'t be used to define a constant');
|
||||
}
|
||||
});
|
||||
return context && context.errors || [];
|
||||
}
|
||||
},
|
||||
'errors.$':{
|
||||
'errors.$': {
|
||||
type: ErrorSchema,
|
||||
},
|
||||
});
|
||||
|
||||
function parseString(string, fn = 'compile'){
|
||||
function parseString(string, fn = 'compile') {
|
||||
let context = new Context();
|
||||
if (!string){
|
||||
return {result: string, context};
|
||||
if (!string) {
|
||||
return { result: string, context };
|
||||
}
|
||||
|
||||
// Parse the string using mathjs
|
||||
@@ -74,11 +74,11 @@ function parseString(string, fn = 'compile'){
|
||||
} catch (e) {
|
||||
let message = prettifyParseError(e);
|
||||
context.error(message);
|
||||
return {context};
|
||||
return { context };
|
||||
}
|
||||
if (!node) return {context};
|
||||
let {result} = resolve(fn, node, {/*empty scope*/}, context);
|
||||
return {result, context}
|
||||
if (!node) return { context };
|
||||
let { result } = resolve(fn, node, {/*empty scope*/ }, context);
|
||||
return { result, context }
|
||||
}
|
||||
|
||||
const ComputedOnlyConstantSchema = new SimpleSchema({});
|
||||
|
||||
@@ -7,7 +7,7 @@ let ContainerSchema = createPropertySchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
carried: {
|
||||
type: Boolean,
|
||||
@@ -21,12 +21,12 @@ let ContainerSchema = createPropertySchema({
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
@@ -35,33 +35,33 @@ let ContainerSchema = createPropertySchema({
|
||||
});
|
||||
|
||||
const ComputedOnlyContainerSchema = createPropertySchema({
|
||||
description: {
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
// Weight of all the contents, zero if `contentsWeightless` is true
|
||||
contentsWeight:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Weight of all the carried contents (some sub-containers might not be carried)
|
||||
// zero if `contentsWeightless` is true
|
||||
carriedWeight:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
contentsValue:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
carriedValue:{
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Weight of all the contents, zero if `contentsWeightless` is true
|
||||
contentsWeight: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Weight of all the carried contents (some sub-containers might not be carried)
|
||||
// zero if `contentsWeightless` is true
|
||||
carriedWeight: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
contentsValue: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
carriedValue: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedContainerSchema = new SimpleSchema()
|
||||
|
||||
@@ -8,10 +8,10 @@ import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
*/
|
||||
let DamageMultiplierSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
damageTypes: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
@@ -23,11 +23,11 @@ let DamageMultiplierSchema = new SimpleSchema({
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
},
|
||||
// The value of the damage multiplier
|
||||
value: {
|
||||
// The value of the damage multiplier
|
||||
value: {
|
||||
type: Number,
|
||||
defaultValue: 0.5,
|
||||
allowedValues: [0, 0.5, 2],
|
||||
defaultValue: 0.5,
|
||||
allowedValues: [0, 0.5, 2],
|
||||
},
|
||||
// Tags which bypass this multiplier (OR)
|
||||
excludeTags: {
|
||||
|
||||
@@ -4,7 +4,7 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
|
||||
const DamageSchema = createPropertySchema({
|
||||
// The roll that determines how much to damage the attribute
|
||||
// The roll that determines how much to damage the attribute
|
||||
// This can be simplified, but only computed when applied
|
||||
amount: {
|
||||
type: 'fieldToCompute',
|
||||
@@ -12,19 +12,19 @@ const DamageSchema = createPropertySchema({
|
||||
defaultValue: '1d8 + strength.modifier',
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
// Who this damage applies to
|
||||
target: {
|
||||
type: String,
|
||||
// Who this damage applies to
|
||||
target: {
|
||||
type: String,
|
||||
defaultValue: 'target',
|
||||
allowedValues: [
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
},
|
||||
damageType: {
|
||||
type: String,
|
||||
},
|
||||
damageType: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
defaultValue: 'slashing',
|
||||
defaultValue: 'slashing',
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
|
||||
@@ -3,19 +3,19 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let FeatureSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
optional: true,
|
||||
},
|
||||
summary: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
summary: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyFeatureSchema = createPropertySchema({
|
||||
|
||||
@@ -6,15 +6,15 @@ const ItemSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// Plural name of the item, if there is more than one
|
||||
plural: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
@@ -28,20 +28,20 @@ const ItemSchema = createPropertySchema({
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
// Value per item in the stack, in gold pieces
|
||||
value: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
optional: true,
|
||||
},
|
||||
// If this item is equipped, it requires attunement
|
||||
requiresAttunement: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
attuned: {
|
||||
attuned: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
@@ -58,14 +58,14 @@ const ItemSchema = createPropertySchema({
|
||||
});
|
||||
|
||||
let ComputedOnlyItemSchema = createPropertySchema({
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedItemSchema = new SimpleSchema()
|
||||
.extend(ItemSchema)
|
||||
.extend(ComputedOnlyItemSchema);
|
||||
.extend(ItemSchema)
|
||||
.extend(ComputedOnlyItemSchema);
|
||||
|
||||
export { ItemSchema, ComputedItemSchema, ComputedOnlyItemSchema };
|
||||
|
||||
@@ -3,19 +3,19 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let NoteSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
summary: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
summary: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyNoteSchema = createPropertySchema({
|
||||
|
||||
@@ -2,28 +2,28 @@ import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let ProficiencySchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The variableNames of the skills, tags, or attributes to apply proficiency to
|
||||
stats: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
// The variableNames of the skills, tags, or attributes to apply proficiency to
|
||||
stats: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.statsToTarget,
|
||||
},
|
||||
'stats.$': {
|
||||
type: String,
|
||||
},
|
||||
'stats.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// A number representing how proficient the character is
|
||||
},
|
||||
// A number representing how proficient the character is
|
||||
// where 0.49 is half rounded down and 0.5 is half rounded up
|
||||
value: {
|
||||
type: Number,
|
||||
allowedValues: [0.49, 0.5, 1, 2],
|
||||
defaultValue: 1,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
allowedValues: [0.49, 0.5, 1, 2],
|
||||
defaultValue: 1,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyProficiencySchema = new SimpleSchema({});
|
||||
|
||||
@@ -23,14 +23,14 @@ import createPropertySchema from '/imports/api/properties/subSchemas/createPrope
|
||||
*/
|
||||
let RollSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
type: String,
|
||||
defaultValue: 'New Roll',
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
type: String,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
defaultValue: 'newRoll',
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
|
||||
@@ -16,14 +16,14 @@ let SavingThrowSchema = createPropertySchema({
|
||||
optional: true,
|
||||
},
|
||||
// Who this saving throw applies to
|
||||
target: {
|
||||
type: String,
|
||||
target: {
|
||||
type: String,
|
||||
defaultValue: 'target',
|
||||
allowedValues: [
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
},
|
||||
},
|
||||
// The variable name of save to roll
|
||||
stat: {
|
||||
type: String,
|
||||
|
||||
@@ -9,10 +9,10 @@ import createPropertySchema from '/imports/api/properties/subSchemas/createPrope
|
||||
*/
|
||||
let SkillSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
// Ignored for skilltype = save
|
||||
variableName: {
|
||||
@@ -22,33 +22,33 @@ let SkillSchema = createPropertySchema({
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
optional: true,
|
||||
},
|
||||
// The variable name of the ability this skill relies on
|
||||
// The variable name of the ability this skill relies on
|
||||
ability: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// What type of skill is this
|
||||
// What type of skill is this
|
||||
skillType: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'skill',
|
||||
'save',
|
||||
'check',
|
||||
'check',
|
||||
'tool',
|
||||
'weapon',
|
||||
'armor',
|
||||
'language',
|
||||
'utility', //not displayed anywhere
|
||||
'utility', //not displayed anywhere
|
||||
],
|
||||
defaultValue: 'skill',
|
||||
},
|
||||
// The base proficiency of this skill
|
||||
baseProficiency: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
// The base proficiency of this skill
|
||||
baseProficiency: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
allowedValues: [0.49, 0.5, 1, 2],
|
||||
},
|
||||
},
|
||||
// The starting value, before effects
|
||||
baseValue: {
|
||||
type: 'fieldToCompute',
|
||||
@@ -56,16 +56,16 @@ let SkillSchema = createPropertySchema({
|
||||
},
|
||||
// Description of what the skill is used for
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlySkillSchema = createPropertySchema({
|
||||
// Computed value of skill to be added to skill rolls
|
||||
// Computed value of skill to be added to skill rolls
|
||||
value: {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
defaultValue: 0,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
@@ -75,33 +75,33 @@ let ComputedOnlySkillSchema = createPropertySchema({
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
// Computed value added by the ability
|
||||
abilityMod: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
// Computed value added by the ability
|
||||
abilityMod: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Computed advantage/disadvantage
|
||||
},
|
||||
// Computed advantage/disadvantage
|
||||
advantage: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
allowedValues: [-1, 0, 1],
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Computed bonus to passive checks
|
||||
// Computed bonus to passive checks
|
||||
passiveBonus: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Computed proficiency multiplier
|
||||
// Computed proficiency multiplier
|
||||
proficiency: {
|
||||
type: Number,
|
||||
allowedValues: [0, 0.49, 0.5, 1, 2],
|
||||
defaultValue: 0,
|
||||
defaultValue: 0,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Compiled text of all conditional benefits
|
||||
@@ -113,7 +113,7 @@ let ComputedOnlySkillSchema = createPropertySchema({
|
||||
'conditionalBenefits.$': {
|
||||
type: String,
|
||||
},
|
||||
// Computed number of things forcing this skill to fail
|
||||
// Computed number of things forcing this skill to fail
|
||||
fail: {
|
||||
type: SimpleSchema.Integer,
|
||||
optional: true,
|
||||
|
||||
@@ -3,17 +3,17 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let SpellListSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// Calculation of how many spells in this list can be prepared
|
||||
maxPrepared: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// Calculation of how many spells in this list can be prepared
|
||||
maxPrepared: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
|
||||
@@ -14,88 +14,88 @@ const magicSchools = [
|
||||
];
|
||||
|
||||
let SpellSchema = new SimpleSchema({})
|
||||
.extend(ActionSchema)
|
||||
.extend({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// If it's always prepared, it doesn't count against the number of spells
|
||||
// prepared in a spell list, and enabled should be true
|
||||
alwaysPrepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
prepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// This spell ignores spell slot rules
|
||||
castWithoutSpellSlots: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
hasAttackRoll: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
castingTime: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'action',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
range: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
duration: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'Instantaneous',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
verbal: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
somatic: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
concentration: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
material: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
ritual: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
max: 9,
|
||||
min: 0,
|
||||
},
|
||||
school: {
|
||||
type: String,
|
||||
defaultValue: 'abjuration',
|
||||
allowedValues: magicSchools,
|
||||
},
|
||||
});
|
||||
.extend(ActionSchema)
|
||||
.extend({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// If it's always prepared, it doesn't count against the number of spells
|
||||
// prepared in a spell list, and enabled should be true
|
||||
alwaysPrepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
prepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// This spell ignores spell slot rules
|
||||
castWithoutSpellSlots: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
hasAttackRoll: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
castingTime: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'action',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
range: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
duration: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'Instantaneous',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
verbal: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
somatic: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
concentration: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
material: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
ritual: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
max: 9,
|
||||
min: 0,
|
||||
},
|
||||
school: {
|
||||
type: String,
|
||||
defaultValue: 'abjuration',
|
||||
allowedValues: magicSchools,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlySpellSchema = new SimpleSchema()
|
||||
.extend(ComputedOnlyActionSchema);
|
||||
.extend(ComputedOnlyActionSchema);
|
||||
|
||||
const ComputedSpellSchema = new SimpleSchema()
|
||||
.extend(SpellSchema)
|
||||
.extend(ComputedOnlySpellSchema);
|
||||
.extend(SpellSchema)
|
||||
.extend(ComputedOnlySpellSchema);
|
||||
|
||||
export { SpellSchema, ComputedOnlySpellSchema, ComputedSpellSchema };
|
||||
|
||||
@@ -41,7 +41,7 @@ const ComputedOnlyToggleSchema = createPropertySchema({
|
||||
});
|
||||
|
||||
const ComputedToggleSchema = new SimpleSchema()
|
||||
.extend(ComputedOnlyToggleSchema)
|
||||
.extend(ToggleSchema);
|
||||
.extend(ComputedOnlyToggleSchema)
|
||||
.extend(ToggleSchema);
|
||||
|
||||
export { ToggleSchema, ComputedOnlyToggleSchema, ComputedToggleSchema };
|
||||
|
||||
@@ -5,31 +5,31 @@ const AdjustmentSchema = new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
autoValue() {
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
// The roll that determines how much to change the attribute
|
||||
// The roll that determines how much to change the attribute
|
||||
adjustment: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: '1',
|
||||
},
|
||||
// Who this adjustment applies to
|
||||
target: {
|
||||
type: String,
|
||||
// Who this adjustment applies to
|
||||
target: {
|
||||
type: String,
|
||||
defaultValue: 'every',
|
||||
allowedValues: [
|
||||
allowedValues: [
|
||||
'self', // the character who took the action
|
||||
'each', // rolled once for `each` target
|
||||
'every', // rolled once and applied to `every` target
|
||||
],
|
||||
},
|
||||
// The stat this rolls applies to, if damage type is set, this is ignored
|
||||
stat: {
|
||||
type: String,
|
||||
},
|
||||
// The stat this rolls applies to, if damage type is set, this is ignored
|
||||
stat: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default AdjustmentSchema;
|
||||
|
||||
@@ -6,10 +6,10 @@ const ErrorSchema = new SimpleSchema({
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.errorMessage,
|
||||
},
|
||||
type: {
|
||||
type: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default ErrorSchema;
|
||||
|
||||
@@ -3,7 +3,7 @@ import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const InlineComputationSchema = new SimpleSchema({
|
||||
// The part between bracers {}
|
||||
// The part between bracers {}
|
||||
calculation: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ResultsSchema from '/imports/api/properties/subSchemas/ResultsSchema.js';
|
||||
|
||||
let RollResultsSchema = new SimpleSchema ({
|
||||
let RollResultsSchema = new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
autoValue() {
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
@@ -17,9 +17,9 @@ let RollResultsSchema = new SimpleSchema ({
|
||||
optional: true,
|
||||
},
|
||||
results: {
|
||||
type: ResultsSchema,
|
||||
defaultValue: {},
|
||||
},
|
||||
type: ResultsSchema,
|
||||
defaultValue: {},
|
||||
},
|
||||
});
|
||||
|
||||
export default RollResultsSchema ;
|
||||
export default RollResultsSchema;
|
||||
|
||||
@@ -3,7 +3,7 @@ import '/imports/api/sharing/sharing.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let SharingSchema = new SimpleSchema({
|
||||
owner: {
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1
|
||||
@@ -12,7 +12,7 @@ let SharingSchema = new SimpleSchema({
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
maxCount: STORAGE_LIMITS.readersCount,
|
||||
maxCount: STORAGE_LIMITS.readersCount,
|
||||
},
|
||||
'readers.$': {
|
||||
type: String,
|
||||
@@ -22,7 +22,7 @@ let SharingSchema = new SimpleSchema({
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
maxCount: STORAGE_LIMITS.writersCount,
|
||||
maxCount: STORAGE_LIMITS.writersCount,
|
||||
},
|
||||
'writers.$': {
|
||||
type: String,
|
||||
|
||||
@@ -9,8 +9,8 @@ import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
const setPublic = new ValidatedMethod({
|
||||
name: 'sharing.setPublic',
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
isPublic: { type: Boolean },
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
@@ -18,19 +18,19 @@ const setPublic = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({docRef, isPublic}){
|
||||
let doc = fetchDocByRef(docRef);
|
||||
assertOwnership(doc, this.userId);
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$set: {public: isPublic},
|
||||
run({ docRef, isPublic }) {
|
||||
let doc = fetchDocByRef(docRef);
|
||||
assertOwnership(doc, this.userId);
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$set: { public: isPublic },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const updateUserSharePermissions = new ValidatedMethod({
|
||||
name: 'sharing.updateUserSharePermissions',
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
userId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
@@ -45,40 +45,40 @@ const updateUserSharePermissions = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({docRef, userId, role}){
|
||||
let doc = fetchDocByRef(docRef);
|
||||
if (role === 'none'){
|
||||
run({ docRef, userId, role }) {
|
||||
let doc = fetchDocByRef(docRef);
|
||||
if (role === 'none') {
|
||||
// only assert ownership if you aren't removing yourself
|
||||
if (this.userId !== userId){
|
||||
if (this.userId !== userId) {
|
||||
assertOwnership(doc, this.userId);
|
||||
}
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$pullAll: { readers: userId, writers: userId },
|
||||
});
|
||||
}
|
||||
if (doc.owner === userId){
|
||||
if (doc.owner === userId) {
|
||||
throw new Meteor.Error('Sharing update failed',
|
||||
'User is already the owner of this document');
|
||||
'User is already the owner of this document');
|
||||
}
|
||||
assertOwnership(doc, this.userId);
|
||||
if (role === 'reader'){
|
||||
if (role === 'reader') {
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$addToSet: { readers: userId },
|
||||
$pullAll: { writers: userId },
|
||||
});
|
||||
} else if (role === 'writer'){
|
||||
} else if (role === 'writer') {
|
||||
return getCollectionByName(docRef.collection).update(docRef.id, {
|
||||
$addToSet: { writers: userId },
|
||||
$pullAll: { readers: userId },
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const transferOwnership = new ValidatedMethod({
|
||||
name: 'sharing.transferOwnership',
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
userId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
@@ -89,31 +89,31 @@ const transferOwnership = new ValidatedMethod({
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({docRef, userId}){
|
||||
run({ docRef, userId }) {
|
||||
let doc = fetchDocByRef(docRef);
|
||||
assertOwnership(doc, this.userId);
|
||||
|
||||
let collection = getCollectionByName(docRef.collection);
|
||||
|
||||
let tier = getUserTier(userId);
|
||||
if (docRef.collection === 'creatures'){
|
||||
if (docRef.collection === 'creatures') {
|
||||
let currentCharacterCount = collection.find({
|
||||
owner: userId,
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
fields: { _id: 1 },
|
||||
}).count();
|
||||
|
||||
if (
|
||||
tier.characterSlots !== -1 &&
|
||||
currentCharacterCount >= tier.characterSlots
|
||||
){
|
||||
) {
|
||||
throw new Meteor.Error('Sharing.methods.transferOwnership.denied',
|
||||
'The new owner is already at their character limit')
|
||||
'The new owner is already at their character limit')
|
||||
}
|
||||
} else if (docRef.collection === 'libraries'){
|
||||
if (!tier.paidBenefits){
|
||||
} else if (docRef.collection === 'libraries') {
|
||||
if (!tier.paidBenefits) {
|
||||
throw new Meteor.Error('Sharing.methods.transferOwnership.denied',
|
||||
'The new owner\'s Patreon tier does not have access to library ownership');
|
||||
'The new owner\'s Patreon tier does not have access to library ownership');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,10 +123,10 @@ const transferOwnership = new ValidatedMethod({
|
||||
});
|
||||
// Then make the user the owner and the current owner a writer
|
||||
return collection.update(docRef.id, {
|
||||
$set: {owner: userId},
|
||||
$set: { owner: userId },
|
||||
$addToSet: { writers: this.userId },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
||||
|
||||
@@ -51,21 +51,21 @@ const sendMessage = new ValidatedMethod({
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run({content, tabletopId}) {
|
||||
run({ content, tabletopId }) {
|
||||
let user = Meteor.user();
|
||||
if (!user) {
|
||||
throw new Meteor.Error('messages.send.denied',
|
||||
'You need to be logged in to send a message');
|
||||
'You need to be logged in to send a message');
|
||||
}
|
||||
assertUserInTabletop(tabletopId, this.userId);
|
||||
|
||||
return Messages.insert({
|
||||
content,
|
||||
content,
|
||||
tabletopId,
|
||||
timestamp: new Date(),
|
||||
userId: user._id,
|
||||
username: user.username,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
@@ -87,24 +87,24 @@ const removeMessages = new ValidatedMethod({
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run({messageId, tabletopId}) {
|
||||
run({ messageId, tabletopId }) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('messages.remove.denied',
|
||||
'You need to be logged in to remove a tabletop');
|
||||
'You need to be logged in to remove a tabletop');
|
||||
}
|
||||
let message = Messages.findOne(messageId);
|
||||
let tabletop = Tabletops.findOne(message.tabletopId);
|
||||
if (this.userId !== message.userId && this.userId !== tabletop.gameMaster){
|
||||
if (this.userId !== message.userId && this.userId !== tabletop.gameMaster) {
|
||||
throw new Meteor.Error('messages.remove.denied',
|
||||
'You don\'t have permission to remove this message');
|
||||
'You don\'t have permission to remove this message');
|
||||
}
|
||||
let removed = Messages.remove({
|
||||
_id: messageId,
|
||||
});
|
||||
_id: messageId,
|
||||
});
|
||||
Creatures.update({
|
||||
tabletop: tabletopId,
|
||||
}, {
|
||||
$unset: {tabletop: 1},
|
||||
$unset: { tabletop: 1 },
|
||||
});
|
||||
return removed;
|
||||
},
|
||||
|
||||
@@ -19,14 +19,14 @@ const insertTabletop = new ValidatedMethod({
|
||||
run() {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('tabletops.insert.denied',
|
||||
'You need to be logged in to insert a tabletop');
|
||||
'You need to be logged in to insert a tabletop');
|
||||
}
|
||||
assertUserHasPaidBenefits(this.userId);
|
||||
assertAdmin(this.userId);
|
||||
|
||||
return Tabletops.insert({
|
||||
gameMaster: this.userId,
|
||||
});
|
||||
gameMaster: this.userId,
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -24,22 +24,22 @@ const removeTabletop = new ValidatedMethod({
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run({tabletopId}) {
|
||||
run({ tabletopId }) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('tabletops.remove.denied',
|
||||
'You need to be logged in to remove a tabletop');
|
||||
'You need to be logged in to remove a tabletop');
|
||||
}
|
||||
assertUserHasPaidBenefits(this.userId);
|
||||
assertUserIsTabletopOwner(tabletopId, this.userId);
|
||||
assertAdmin(this.userId);
|
||||
|
||||
let removed = Tabletops.remove({
|
||||
_id: tabletopId,
|
||||
});
|
||||
_id: tabletopId,
|
||||
});
|
||||
Creatures.update({
|
||||
tabletop: tabletopId,
|
||||
}, {
|
||||
$unset: {tabletop: 1},
|
||||
$unset: { tabletop: 1 },
|
||||
});
|
||||
return removed;
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@ const userSchema = new SimpleSchema({
|
||||
},
|
||||
'subscribedLibraries.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedLibraryCollections: {
|
||||
type: Array,
|
||||
@@ -84,81 +84,81 @@ const userSchema = new SimpleSchema({
|
||||
},
|
||||
'subscribedLibraryCollections.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedCharacters: {
|
||||
subscribedCharacters: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
max: 100,
|
||||
},
|
||||
'subscribedCharacters.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
fileStorageUsed: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
preferences: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
'preferences.swapAbilityScoresAndModifiers': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
'preferences.hidePropertySelectDialogHelp': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
preferences: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
'preferences.swapAbilityScoresAndModifiers': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
'preferences.hidePropertySelectDialogHelp': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.attachSchema(userSchema);
|
||||
|
||||
Meteor.users.generateApiKey = new ValidatedMethod({
|
||||
name: 'users.generateApiKey',
|
||||
name: 'users.generateApiKey',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run(){
|
||||
if(Meteor.isClient) return;
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run() {
|
||||
if (Meteor.isClient) return;
|
||||
var user = Meteor.users.findOne(this.userId);
|
||||
if (!user) return;
|
||||
if (user && user.apiKey) return;
|
||||
var apiKey = Random.id(30);
|
||||
Meteor.users.update(this.userId, {$set: {apiKey}});
|
||||
Meteor.users.update(this.userId, { $set: { apiKey } });
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.setDarkMode = new ValidatedMethod({
|
||||
name: 'users.setDarkMode',
|
||||
name: 'users.setDarkMode',
|
||||
validate: new SimpleSchema({
|
||||
darkMode: { type: Boolean },
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({darkMode}){
|
||||
darkMode: { type: Boolean },
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ darkMode }) {
|
||||
if (!this.userId) return;
|
||||
Meteor.users.update(this.userId, {$set: {darkMode}});
|
||||
Meteor.users.update(this.userId, { $set: { darkMode } });
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.sendVerificationEmail = new ValidatedMethod({
|
||||
name: 'users.sendVerificationEmail',
|
||||
validate: new SimpleSchema({
|
||||
userId:{
|
||||
userId: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
@@ -166,12 +166,12 @@ Meteor.users.sendVerificationEmail = new ValidatedMethod({
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({userId, address}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ userId, address }) {
|
||||
userId = this.userId || userId;
|
||||
let user = Meteor.users.findOne(userId);
|
||||
if (!user) {
|
||||
@@ -189,140 +189,140 @@ Meteor.users.sendVerificationEmail = new ValidatedMethod({
|
||||
Meteor.users.canPickUsername = new ValidatedMethod({
|
||||
name: 'users.canPickUsername',
|
||||
validate: userSchema.pick('username').validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({username}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ username }) {
|
||||
if (Meteor.isClient) return;
|
||||
let user = Accounts.findUserByUsername(username);
|
||||
// You can pick your own username
|
||||
if (user && user._id === this.userId){
|
||||
return false;
|
||||
}
|
||||
// You can pick your own username
|
||||
if (user && user._id === this.userId) {
|
||||
return false;
|
||||
}
|
||||
return !!user;
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.setUsername = new ValidatedMethod({
|
||||
name: 'users.setUsername',
|
||||
name: 'users.setUsername',
|
||||
validate: userSchema.pick('username').validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({username}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ username }) {
|
||||
if (!this.userId) throw 'Can only set your username if logged in';
|
||||
if (Meteor.isClient) return;
|
||||
return Accounts.setUsername(this.userId, username)
|
||||
if (Meteor.isClient) return;
|
||||
return Accounts.setUsername(this.userId, username)
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.setPreference = new ValidatedMethod({
|
||||
name: 'users.setPreference',
|
||||
name: 'users.setPreference',
|
||||
validate: new SimpleSchema({
|
||||
preference:{
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: SimpleSchema.oneOf(Boolean),
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({preference, value}){
|
||||
preference: {
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: SimpleSchema.oneOf(Boolean),
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ preference, value }) {
|
||||
if (!this.userId) throw 'You can only set preferences once logged in';
|
||||
let prefPath = `preferences.${preference}`
|
||||
if (value == true){
|
||||
return Meteor.users.update(this.userId, {
|
||||
$set: {[prefPath]: true},
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$unset: {[prefPath]: 1},
|
||||
});
|
||||
}
|
||||
let prefPath = `preferences.${preference}`
|
||||
if (value == true) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$set: { [prefPath]: true },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$unset: { [prefPath]: 1 },
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.subscribeToLibrary = new ValidatedMethod({
|
||||
name: 'users.subscribeToLibrary',
|
||||
name: 'users.subscribeToLibrary',
|
||||
validate: new SimpleSchema({
|
||||
libraryId:{
|
||||
libraryId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({libraryId, subscribe}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ libraryId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe){
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: {subscribedLibraries: libraryId},
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: {subscribedLibraries: libraryId},
|
||||
});
|
||||
}
|
||||
if (subscribe) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraries: libraryId },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraries: libraryId },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.subscribeToLibraryCollection = new ValidatedMethod({
|
||||
name: 'users.subscribeToLibraryCollection',
|
||||
name: 'users.subscribeToLibraryCollection',
|
||||
validate: new SimpleSchema({
|
||||
libraryCollectionId:{
|
||||
libraryCollectionId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({libraryCollectionId, subscribe}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ libraryCollectionId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe){
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: {subscribedLibraryCollections: libraryCollectionId},
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: {subscribedLibraryCollections: libraryCollectionId},
|
||||
});
|
||||
}
|
||||
if (subscribe) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.findUserByUsernameOrEmail = new ValidatedMethod({
|
||||
name: 'users.findUserByUsernameOrEmail',
|
||||
validate: new SimpleSchema({
|
||||
usernameOrEmail:{
|
||||
usernameOrEmail: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({usernameOrEmail}){
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ usernameOrEmail }) {
|
||||
if (Meteor.isClient) return;
|
||||
let user = Accounts.findUserByUsername(usernameOrEmail) ||
|
||||
Accounts.findUserByEmail(usernameOrEmail);
|
||||
|
||||
@@ -3,8 +3,8 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const addEmail = new ValidatedMethod({
|
||||
name: 'users.addEmail',
|
||||
validate: new SimpleSchema({
|
||||
name: 'users.addEmail',
|
||||
validate: new SimpleSchema({
|
||||
email: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Email,
|
||||
@@ -15,20 +15,20 @@ const addEmail = new ValidatedMethod({
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({email}){
|
||||
run({ email }) {
|
||||
const userId = Meteor.userId();
|
||||
const user = Meteor.users.findOne(userId);
|
||||
if (!user) throw new Meteor.Error('No user',
|
||||
'You must be logged in to add an email address');
|
||||
if (user.emails && user.emails.length >= 2){
|
||||
'You must be logged in to add an email address');
|
||||
if (user.emails && user.emails.length >= 2) {
|
||||
throw new Meteor.Error('Emails full',
|
||||
'You may only have up to 2 email addresses per account');
|
||||
'You may only have up to 2 email addresses per account');
|
||||
}
|
||||
if (Meteor.isServer){
|
||||
if (Meteor.isServer) {
|
||||
Accounts.addEmail(userId, email);
|
||||
Accounts.sendVerificationEmail(userId, email);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default addEmail;
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Libraries, {removeLibaryWork} from '/imports/api/library/Libraries.js';
|
||||
import Libraries, { removeLibaryWork } from '/imports/api/library/Libraries.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import {removeCreatureWork} from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
|
||||
Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||
name: 'users.deleteMyAccount',
|
||||
validate: null,
|
||||
name: 'users.deleteMyAccount',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run(){
|
||||
run() {
|
||||
let userId = Meteor.userId();
|
||||
if (!userId) throw new Meteor.Error('No user',
|
||||
'You must be logged in to delete your account');
|
||||
|
||||
// Delete all creatures
|
||||
let creatures = Creatures.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||
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},
|
||||
{ writers: userId },
|
||||
{ readers: userId },
|
||||
],
|
||||
}, {
|
||||
$pull: {
|
||||
@@ -37,14 +37,14 @@ Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||
});
|
||||
|
||||
// Delete all libraries
|
||||
let libraries = Libraries.find({owner: userId}, {fields: {_id: 1}}).fetch();
|
||||
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},
|
||||
{ writers: userId },
|
||||
{ readers: userId },
|
||||
],
|
||||
}, {
|
||||
$pull: {
|
||||
@@ -57,5 +57,5 @@ Meteor.users.deleteMyAccount = new ValidatedMethod({
|
||||
|
||||
// delete the account
|
||||
Meteor.users.remove(userId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,8 +3,8 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
|
||||
const removeEmail = new ValidatedMethod({
|
||||
name: 'users.removeEmail',
|
||||
validate: new SimpleSchema({
|
||||
name: 'users.removeEmail',
|
||||
validate: new SimpleSchema({
|
||||
email: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Email,
|
||||
@@ -15,23 +15,23 @@ const removeEmail = new ValidatedMethod({
|
||||
numRequests: 1,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({email}){
|
||||
run({ email }) {
|
||||
const userId = Meteor.userId();
|
||||
const user = Meteor.users.findOne(userId);
|
||||
if (!user) throw new Meteor.Error('No user',
|
||||
'You must be logged in to remove an email address');
|
||||
if (!user.emails){
|
||||
'You must be logged in to remove an email address');
|
||||
if (!user.emails) {
|
||||
throw new Meteor.Error('No email to remove',
|
||||
'No email addresses are associated with this account');
|
||||
'No email addresses are associated with this account');
|
||||
}
|
||||
if (user.emails.length == 1){
|
||||
if (user.emails.length == 1) {
|
||||
throw new Meteor.Error('Can\'t remove last email',
|
||||
'You may not remove the last email address from your account');
|
||||
'You may not remove the last email address from your account');
|
||||
}
|
||||
if (Meteor.isServer){
|
||||
if (Meteor.isServer) {
|
||||
Accounts.removeEmail(userId, email);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default removeEmail;
|
||||
|
||||
@@ -10,7 +10,7 @@ const dbVersionToGitVersion = {
|
||||
|
||||
const getVersion = new ValidatedMethod({
|
||||
name: 'admin.getVersion',
|
||||
validate: null,
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Migrations } from 'meteor/percolate:migrations';
|
||||
|
||||
const migrateTo = new ValidatedMethod({
|
||||
name: 'admin.migrateTo',
|
||||
validate: new SimpleSchema({
|
||||
validate: new SimpleSchema({
|
||||
version: {
|
||||
type: SimpleSchema.oneOf(
|
||||
SimpleSchema.Integer,
|
||||
@@ -19,7 +19,7 @@ const migrateTo = new ValidatedMethod({
|
||||
numRequests: 1,
|
||||
timeInterval: 10000,
|
||||
},
|
||||
run({version}) {
|
||||
run({ version }) {
|
||||
if (Meteor.isClient) return;
|
||||
assertAdmin(this.userId);
|
||||
Migrations.migrateTo(version);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js';
|
||||
|
||||
const validateDatabase = new ValidatedMethod({
|
||||
name: 'validateDatabase',
|
||||
validate: null,
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 1,
|
||||
@@ -23,8 +23,8 @@ const validateDatabase = new ValidatedMethod({
|
||||
const schema = collection.instance.simpleSchema(doc);
|
||||
let cleanDoc = schema.clean(doc);
|
||||
try {
|
||||
schema.validate(cleanDoc, {modifier: false});
|
||||
} catch (e){
|
||||
schema.validate(cleanDoc, { modifier: false });
|
||||
} catch (e) {
|
||||
console.log(collection.name, doc._id, e.message || e.reason || e.toString());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@{%
|
||||
import node from './parseTree/_index.js';
|
||||
|
||||
import moo from 'moo';
|
||||
import moo from 'moo';
|
||||
|
||||
const lexer = moo.compile({
|
||||
number: /[0-9]+(?:\.[0-9]+)?/,
|
||||
|
||||
@@ -2,23 +2,23 @@ import resolve, { traverse, toString, map } from '../resolve';
|
||||
import error from './error';
|
||||
|
||||
const indexNode = {
|
||||
create({array, index}) {
|
||||
return {
|
||||
create({ array, index }) {
|
||||
return {
|
||||
parseType: 'index',
|
||||
array,
|
||||
index,
|
||||
}
|
||||
},
|
||||
resolve(fn, node, scope, context){
|
||||
let {result: index} = resolve(fn, node.index, scope, context);
|
||||
let {result: array} = resolve(fn, node.array, scope, context);
|
||||
resolve(fn, node, scope, context) {
|
||||
let { result: index } = resolve(fn, node.index, scope, context);
|
||||
let { result: array } = resolve(fn, node.array, scope, context);
|
||||
|
||||
if (
|
||||
index.valueType === 'number' &&
|
||||
Number.isInteger(index.value) &&
|
||||
array.parseType === 'array'
|
||||
){
|
||||
if (index.value < 1 || index.value > array.values.length){
|
||||
) {
|
||||
if (index.value < 1 || index.value > array.values.length) {
|
||||
context.error({
|
||||
type: 'warning',
|
||||
message: `Index of ${index.value} is out of range for an array` +
|
||||
@@ -26,11 +26,11 @@ const indexNode = {
|
||||
});
|
||||
}
|
||||
let selection = array.values[index.value - 1];
|
||||
if (selection){
|
||||
if (selection) {
|
||||
return resolve(fn, selection, scope, context);
|
||||
}
|
||||
} else if (fn === 'reduce'){
|
||||
if (array.parseType !== 'array'){
|
||||
} else if (fn === 'reduce') {
|
||||
if (array.parseType !== 'array') {
|
||||
const message = `Can not get the index of a non-array node: ${toString(node.array)} = ${toString(array)}`
|
||||
context.error(message);
|
||||
return {
|
||||
@@ -40,7 +40,7 @@ const indexNode = {
|
||||
}),
|
||||
context,
|
||||
};
|
||||
} else if (!index.isInteger){
|
||||
} else if (!index.isInteger) {
|
||||
const message = `${toString(array)} is not an integer index of the array`
|
||||
context.error(message);
|
||||
return {
|
||||
@@ -60,17 +60,17 @@ const indexNode = {
|
||||
context,
|
||||
};
|
||||
},
|
||||
toString(node){
|
||||
toString(node) {
|
||||
return `${toString(node.array)}[${toString(node.index)}]`;
|
||||
},
|
||||
traverse(node, fn){
|
||||
traverse(node, fn) {
|
||||
fn(node);
|
||||
traverse(node.array, fn);
|
||||
traverse(node.index, fn);
|
||||
},
|
||||
map(node, fn){
|
||||
map(node, fn) {
|
||||
const resultingNode = fn(node);
|
||||
if (resultingNode === node){
|
||||
if (resultingNode === node) {
|
||||
node.array = map(node.array, fn);
|
||||
node.index = map(node.index, fn);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import constant from './constant.js';
|
||||
|
||||
const rollArray = {
|
||||
create({values, diceSize, diceNum}) {
|
||||
return {
|
||||
create({ values, diceSize, diceNum }) {
|
||||
return {
|
||||
parseType: 'rollArray',
|
||||
values,
|
||||
diceSize,
|
||||
diceNum,
|
||||
};
|
||||
},
|
||||
compile(node, scope, context){
|
||||
compile(node, scope, context) {
|
||||
return {
|
||||
result: node,
|
||||
context
|
||||
};
|
||||
},
|
||||
toString(node){
|
||||
toString(node) {
|
||||
return `${node.diceNum || ''}d${node.diceSize} [ ${node.values.join(', ')} ]`;
|
||||
},
|
||||
reduce(node, scope, context){
|
||||
reduce(node, scope, context) {
|
||||
const total = node.values.reduce((a, b) => a + b, 0);
|
||||
return {
|
||||
result: constant.create({
|
||||
|
||||
@@ -6,7 +6,7 @@ import { SyncedCron } from 'meteor/littledata:synced-cron';
|
||||
Meteor.startup(() => {
|
||||
const collections = [
|
||||
CreatureProperties,
|
||||
LibraryNodes,
|
||||
LibraryNodes,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -14,15 +14,15 @@ Meteor.startup(() => {
|
||||
* and were not restored
|
||||
* @return {Number} Number of documents removed
|
||||
*/
|
||||
const deleteOldSoftRemovedDocs = function(){
|
||||
const deleteOldSoftRemovedDocs = function () {
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||
const yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||
collections.forEach(collection => {
|
||||
collection.remove({
|
||||
removed: true,
|
||||
removedAt: {$lt: yesterday} // dates *before* yesterday
|
||||
}, function(error){
|
||||
if (error){
|
||||
removedAt: { $lt: yesterday } // dates *before* yesterday
|
||||
}, function (error) {
|
||||
if (error) {
|
||||
console.error(JSON.stringify(error, null, 2));
|
||||
}
|
||||
});
|
||||
@@ -31,7 +31,7 @@ Meteor.startup(() => {
|
||||
|
||||
SyncedCron.add({
|
||||
name: 'deleteSoftRemovedDocs',
|
||||
schedule: function(parser) {
|
||||
schedule: function (parser) {
|
||||
return parser.text('every 10 minutes');
|
||||
},
|
||||
job: deleteOldSoftRemovedDocs,
|
||||
@@ -42,7 +42,7 @@ Meteor.startup(() => {
|
||||
// Add a method to manually trigger removal
|
||||
Meteor.methods({
|
||||
deleteOldSoftRemovedDocs() {
|
||||
assertAdmin(this.userId);
|
||||
assertAdmin(this.userId);
|
||||
this.unblock();
|
||||
deleteOldSoftRemovedDocs();
|
||||
},
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import Icons from '/imports/api/icons/Icons.js';
|
||||
|
||||
Meteor.publish('sampleIcons', function(){
|
||||
return Icons.find({}, {limit: 50});
|
||||
Meteor.publish('sampleIcons', function () {
|
||||
return Icons.find({}, { limit: 50 });
|
||||
});
|
||||
|
||||
Meteor.publish('searchIcons', function(searchValue) {
|
||||
Meteor.publish('searchIcons', function (searchValue) {
|
||||
// Don't publish anything if there's no search value
|
||||
if (!searchValue) {
|
||||
return [];
|
||||
}
|
||||
return Icons.find(
|
||||
{ $text: {$search: searchValue} },
|
||||
{ $text: { $search: searchValue } },
|
||||
{
|
||||
// relevant documents have a higher score.
|
||||
fields: {
|
||||
|
||||
@@ -2,31 +2,33 @@ import SimpleSchema from 'simpl-schema';
|
||||
import '/imports/api/users/Users.js';
|
||||
import Invites from '/imports/api/users/Invites.js';
|
||||
|
||||
Meteor.publish('user', function(){
|
||||
return [
|
||||
Meteor.users.find(this.userId, {fields: {
|
||||
roles: 1,
|
||||
username: 1,
|
||||
apiKey: 1,
|
||||
darkMode: 1,
|
||||
subscribedLibraries: 1,
|
||||
subscribedLibraryCollections: 1,
|
||||
fileStorageUsed: 1,
|
||||
profile: 1,
|
||||
preferences: 1,
|
||||
'services.patreon.id': 1,
|
||||
'services.patreon.entitledCents': 1,
|
||||
'services.patreon.entitledCentsOverride': 1,
|
||||
'services.google.id': 1,
|
||||
'services.google.picture': 1,
|
||||
'services.google.name': 1,
|
||||
'services.google.email': 1,
|
||||
'services.google.locale': 1,
|
||||
}}),
|
||||
Meteor.publish('user', function () {
|
||||
return [
|
||||
Meteor.users.find(this.userId, {
|
||||
fields: {
|
||||
roles: 1,
|
||||
username: 1,
|
||||
apiKey: 1,
|
||||
darkMode: 1,
|
||||
subscribedLibraries: 1,
|
||||
subscribedLibraryCollections: 1,
|
||||
fileStorageUsed: 1,
|
||||
profile: 1,
|
||||
preferences: 1,
|
||||
'services.patreon.id': 1,
|
||||
'services.patreon.entitledCents': 1,
|
||||
'services.patreon.entitledCentsOverride': 1,
|
||||
'services.google.id': 1,
|
||||
'services.google.picture': 1,
|
||||
'services.google.name': 1,
|
||||
'services.google.email': 1,
|
||||
'services.google.locale': 1,
|
||||
}
|
||||
}),
|
||||
Invites.find({
|
||||
$or: [
|
||||
{inviter: this.userId},
|
||||
{invitee: this.userId}
|
||||
{ inviter: this.userId },
|
||||
{ invitee: this.userId }
|
||||
],
|
||||
}, {
|
||||
fields: {
|
||||
@@ -41,19 +43,19 @@ let userIdsSchema = new SimpleSchema({
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'ids.$':{
|
||||
'ids.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
}
|
||||
})
|
||||
|
||||
Meteor.publish('userPublicProfiles', function(ids){
|
||||
userIdsSchema.validate({ids});
|
||||
if (!this.userId || !ids) return this.ready();
|
||||
return Meteor.users.find({
|
||||
_id: {$in: ids}
|
||||
},{
|
||||
fields: {username: 1},
|
||||
sort: {username: 1},
|
||||
});
|
||||
Meteor.publish('userPublicProfiles', function (ids) {
|
||||
userIdsSchema.validate({ ids });
|
||||
if (!this.userId || !ids) return this.ready();
|
||||
return Meteor.users.find({
|
||||
_id: { $in: ids }
|
||||
}, {
|
||||
fields: { username: 1 },
|
||||
sort: { username: 1 },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,41 +12,46 @@
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
props: {
|
||||
wideColumns: Boolean,
|
||||
},
|
||||
props: {
|
||||
wideColumns: Boolean,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.column-layout {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 240px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
.column-layout.wide-columns {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 320px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
.column-layout > div, .column-layout > span > div {
|
||||
/*
|
||||
.column-layout {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 240px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.column-layout.wide-columns {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 320px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.column-layout>div,
|
||||
.column-layout>span>div {
|
||||
/*
|
||||
Table and width set because firefox does not support break-inside: avoid
|
||||
*/
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
-webkit-backface-visibility: hidden;
|
||||
-webkit-transform: translateX(0);
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
padding: 4px;
|
||||
}
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
transform: translateX(0);
|
||||
-webkit-transform: translateX(0);
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
padding: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -65,102 +65,103 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
open: Boolean,
|
||||
flat: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editValue: this.value,
|
||||
operation: 'set',
|
||||
hover: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
open: {
|
||||
immediate: true,
|
||||
handler(isOpen) {
|
||||
if (isOpen) this.resetData();
|
||||
},
|
||||
open: Boolean,
|
||||
flat: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editValue: this.value,
|
||||
operation: 'set',
|
||||
hover: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
open: {
|
||||
immediate: true,
|
||||
handler(isOpen) {
|
||||
if (isOpen) this.resetData();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
resetData(){
|
||||
this.editValue = this.value;
|
||||
this.operation = 'set';
|
||||
// this.$nextTick didn't work, using timeout instead did
|
||||
setTimeout(() => {
|
||||
if (this.$refs.editInput){
|
||||
this.$refs.editInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
cancelEdit() {
|
||||
this.$emit('close');
|
||||
},
|
||||
commitEdit() {
|
||||
this.editing = false;
|
||||
let value = +this.$refs.editInput.lazyValue;
|
||||
if (this.operation === 'add') {
|
||||
value = -value;
|
||||
}
|
||||
let type = this.operation === 'set' ? 'set' : 'increment';
|
||||
this.$emit('change', { type, value });
|
||||
},
|
||||
operationIcon(operation) {
|
||||
switch (operation) {
|
||||
case 'set':
|
||||
return 'mdi-forward';
|
||||
case 'add':
|
||||
return 'mdi-plus';
|
||||
case 'subtract':
|
||||
return 'mdi-minus';
|
||||
}
|
||||
},
|
||||
toggleAdd(){
|
||||
this.operation = (this.operation === 'add') ? 'set': 'add';
|
||||
},
|
||||
toggleSubtract(){
|
||||
this.operation = (this.operation === 'subtract') ? 'set': 'subtract';
|
||||
},
|
||||
keypress(event) {
|
||||
let digitsOnly = /[0-9]/;
|
||||
let key = event.key;
|
||||
if (key === '+') {
|
||||
this.toggleAdd();
|
||||
event.preventDefault();
|
||||
} else if (key === '-') {
|
||||
this.toggleSubtract();
|
||||
event.preventDefault();
|
||||
} else if (key === 'Enter') {
|
||||
this.commitEdit();
|
||||
} else if (!digitsOnly.test(key)){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
input(value){
|
||||
if (+value < 0){
|
||||
this.editValue = -value;
|
||||
this.operation = 'subtract';
|
||||
},
|
||||
methods: {
|
||||
resetData() {
|
||||
this.editValue = this.value;
|
||||
this.operation = 'set';
|
||||
// this.$nextTick didn't work, using timeout instead did
|
||||
setTimeout(() => {
|
||||
if (this.$refs.editInput) {
|
||||
this.$refs.editInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
},
|
||||
cancelEdit() {
|
||||
this.$emit('close');
|
||||
},
|
||||
commitEdit() {
|
||||
this.editing = false;
|
||||
let value = +this.$refs.editInput.lazyValue;
|
||||
if (this.operation === 'add') {
|
||||
value = -value;
|
||||
}
|
||||
}
|
||||
};
|
||||
let type = this.operation === 'set' ? 'set' : 'increment';
|
||||
this.$emit('change', { type, value });
|
||||
},
|
||||
operationIcon(operation) {
|
||||
switch (operation) {
|
||||
case 'set':
|
||||
return 'mdi-forward';
|
||||
case 'add':
|
||||
return 'mdi-plus';
|
||||
case 'subtract':
|
||||
return 'mdi-minus';
|
||||
}
|
||||
},
|
||||
toggleAdd() {
|
||||
this.operation = (this.operation === 'add') ? 'set' : 'add';
|
||||
},
|
||||
toggleSubtract() {
|
||||
this.operation = (this.operation === 'subtract') ? 'set' : 'subtract';
|
||||
},
|
||||
keypress(event) {
|
||||
let digitsOnly = /[0-9]/;
|
||||
let key = event.key;
|
||||
if (key === '+') {
|
||||
this.toggleAdd();
|
||||
event.preventDefault();
|
||||
} else if (key === '-') {
|
||||
this.toggleSubtract();
|
||||
event.preventDefault();
|
||||
} else if (key === 'Enter') {
|
||||
this.commitEdit();
|
||||
} else if (!digitsOnly.test(key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
input(value) {
|
||||
if (+value < 0) {
|
||||
this.editValue = -value;
|
||||
this.operation = 'subtract';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filled.theme--light {
|
||||
background: #fff !important;
|
||||
}
|
||||
.filled.theme--dark {
|
||||
background: #424242 !important;
|
||||
}
|
||||
.filled.theme--light {
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
.filled.theme--dark {
|
||||
background: #424242 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,20 +8,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { marked } from 'marked';
|
||||
import { marked } from 'marked';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
markdown: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
compiledMarkdown() {
|
||||
if (!this.markdown) return;
|
||||
return marked(this.markdown);
|
||||
},
|
||||
export default {
|
||||
props: {
|
||||
markdown: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
compiledMarkdown() {
|
||||
if (!this.markdown) return;
|
||||
return marked(this.markdown);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -27,54 +27,58 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
import isDarkColor from '/imports/ui/utility/isDarkColor.js';
|
||||
import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardHighlight,
|
||||
export default {
|
||||
components: {
|
||||
CardHighlight,
|
||||
},
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default() {
|
||||
return getThemeColor('secondary');
|
||||
},
|
||||
},
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default(){
|
||||
return getThemeColor('secondary');
|
||||
},
|
||||
},
|
||||
transparentToolbar: Boolean,
|
||||
},
|
||||
data(){ return {
|
||||
transparentToolbar: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hovering: false,
|
||||
}},
|
||||
computed: {
|
||||
isDark(){
|
||||
return isDarkColor(this.color);
|
||||
},
|
||||
hasClickListener(){
|
||||
return this.$listeners && !!this.$listeners.click;
|
||||
},
|
||||
hasToolbarClickListener(){
|
||||
return this.$listeners && !!this.$listeners.toolbarclick;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hoverToolbar(val){
|
||||
this.hovering = this.$listeners &&
|
||||
!!this.$listeners.toolbarclick &&
|
||||
val;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isDark() {
|
||||
return isDarkColor(this.color);
|
||||
},
|
||||
hasClickListener() {
|
||||
return this.$listeners && !!this.$listeners.click;
|
||||
},
|
||||
hasToolbarClickListener() {
|
||||
return this.$listeners && !!this.$listeners.toolbarclick;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hoverToolbar(val) {
|
||||
this.hovering = this.$listeners &&
|
||||
!!this.$listeners.toolbarclick &&
|
||||
val;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.toolbar-card .v-toolbar__title {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.toolbar-card {
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
.toolbar-card.transparent-toolbar .theme--dark.v-toolbar.v-sheet {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
@@ -34,23 +34,26 @@ import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
data(){return {
|
||||
menu: false,
|
||||
};},
|
||||
computed: {
|
||||
formattedSafeValue(){
|
||||
return format(this.safeValue, 'YYYY-MM-DD')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dateInput(e){
|
||||
this.menu = false;
|
||||
this.input(e);
|
||||
},
|
||||
},
|
||||
mixins: [SmartInput],
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formattedSafeValue() {
|
||||
return format(this.safeValue, 'YYYY-MM-DD')
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dateInput(e) {
|
||||
this.menu = false;
|
||||
this.input(e);
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -87,7 +87,7 @@ export default {
|
||||
SvgIcon,
|
||||
},
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: 'Icon',
|
||||
@@ -96,39 +96,42 @@ export default {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
menu: false,
|
||||
searchString: '',
|
||||
icons: [],
|
||||
};},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menu: false,
|
||||
searchString: '',
|
||||
icons: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
menu(value){
|
||||
if (value){
|
||||
menu(value) {
|
||||
if (value) {
|
||||
setTimeout(() => {
|
||||
if (this.$refs.iconSearchField){
|
||||
if (this.$refs.iconSearchField) {
|
||||
this.$refs.iconSearchField.$children[0].focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
search(value, ack){
|
||||
methods: {
|
||||
search(value, ack) {
|
||||
this.searchString = value;
|
||||
this.icons = [];
|
||||
findIcons.call({search: value}, (error, result) => {
|
||||
findIcons.call({ search: value }, (error, result) => {
|
||||
ack(error);
|
||||
this.icons = result;
|
||||
});
|
||||
},
|
||||
select(icon){
|
||||
select(icon) {
|
||||
this.menu = false;
|
||||
this.change(icon);
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -21,34 +21,36 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
multiple: Boolean,
|
||||
},
|
||||
data(){ return {
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
multiple: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchInput: '',
|
||||
}},
|
||||
computed: {
|
||||
// Multiple combobox gets a long default debounce time while single
|
||||
// value gets a shorter one
|
||||
debounceTime() {
|
||||
if (Number.isFinite(this.debounce)){
|
||||
return this.debounce;
|
||||
} else if (Number.isFinite(this.context.debounceTime)){
|
||||
return this.context.debounceTime;
|
||||
} else {
|
||||
return this.multiple ? 1000 : 100;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
customChange(val){
|
||||
this.input(val);
|
||||
this.searchInput = '';
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// Multiple combobox gets a long default debounce time while single
|
||||
// value gets a shorter one
|
||||
debounceTime() {
|
||||
if (Number.isFinite(this.debounce)) {
|
||||
return this.debounce;
|
||||
} else if (Number.isFinite(this.context.debounceTime)) {
|
||||
return this.context.debounceTime;
|
||||
} else {
|
||||
return this.multiple ? 1000 : 100;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
customChange(val) {
|
||||
this.input(val);
|
||||
this.searchInput = '';
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -13,16 +13,18 @@ export default {
|
||||
context: { default: {} }
|
||||
},
|
||||
inheritAttrs: false,
|
||||
data(){ return {
|
||||
error: false,
|
||||
ackErrors: null,
|
||||
rulesErrors: null,
|
||||
focused: false,
|
||||
loading: false,
|
||||
dirty: false,
|
||||
safeValue: this.value,
|
||||
inputValue: this.value,
|
||||
};},
|
||||
data() {
|
||||
return {
|
||||
error: false,
|
||||
ackErrors: null,
|
||||
rulesErrors: null,
|
||||
focused: false,
|
||||
loading: false,
|
||||
dirty: false,
|
||||
safeValue: this.value,
|
||||
inputValue: this.value,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
value: [String, Number, Date, Array, Object, Boolean],
|
||||
errorMessages: [String, Array],
|
||||
@@ -34,11 +36,11 @@ export default {
|
||||
rules: Array,
|
||||
},
|
||||
watch: {
|
||||
focused(newFocus){
|
||||
focused(newFocus) {
|
||||
// If the value updated while we were focused, show it now on defocus
|
||||
// but not if we are waiting for our own writes to get persisted
|
||||
// and not if there is an error in our input
|
||||
if (!newFocus && !this.dirty && !this.error){
|
||||
if (!newFocus && !this.dirty && !this.error) {
|
||||
this.forceSafeValueUpdate();
|
||||
}
|
||||
// Start the loading bar on defocus if the input is dirty
|
||||
@@ -48,118 +50,118 @@ export default {
|
||||
!newFocus &&
|
||||
this.dirty &&
|
||||
!(this.rulesErrors && this.rulesErrors.length)
|
||||
){
|
||||
) {
|
||||
if (this.hasChangeListener) this.loading = true;
|
||||
}
|
||||
},
|
||||
dirty(newDirty){
|
||||
dirty(newDirty) {
|
||||
// Our changes were acknowledged, weren't in error, and we aren't focused,
|
||||
// make sure the internal value matches the database value
|
||||
if (!newDirty && !this.focused && !this.error){
|
||||
if (!newDirty && !this.focused && !this.error) {
|
||||
this.forceSafeValueUpdate();
|
||||
}
|
||||
},
|
||||
value(newValue){
|
||||
value(newValue) {
|
||||
if (
|
||||
!this.focused &&
|
||||
!(this.rulesErrors && this.rulesErrors.length)
|
||||
){
|
||||
) {
|
||||
this.safeValue = newValue;
|
||||
}
|
||||
},
|
||||
safeValue(){
|
||||
safeValue() {
|
||||
// The safe value only gets updated from the parent, so it must be valid
|
||||
this.error = false;
|
||||
this.ackErrors = null;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
input(val){
|
||||
input(val) {
|
||||
this.$emit('input', val);
|
||||
this.inputValue = val;
|
||||
this.dirty = true;
|
||||
|
||||
// Apply the rules if there are any
|
||||
this.rulesErrors = null;
|
||||
if (this.rules && this.rules.length){
|
||||
if (this.rules && this.rules.length) {
|
||||
this.rules.forEach(rule => {
|
||||
const result = rule(val);
|
||||
if (typeof result === 'string'){
|
||||
if (typeof result === 'string') {
|
||||
if (!this.rulesErrors) this.rulesErrors = [];
|
||||
this.rulesErrors.push(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.rulesErrors){
|
||||
if (this.rulesErrors) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.debouncedChange(val);
|
||||
},
|
||||
acknowledgeChange(error){
|
||||
acknowledgeChange(error) {
|
||||
this.loading = false;
|
||||
this.dirty = false;
|
||||
this.error = !!error;
|
||||
if (!error){
|
||||
this.ackErrors = null;
|
||||
} else if (typeof error === 'string'){
|
||||
this.ackErrors = error;
|
||||
} else if (error.reason){
|
||||
if (!error) {
|
||||
this.ackErrors = null;
|
||||
} else if (typeof error === 'string') {
|
||||
this.ackErrors = error;
|
||||
} else if (error.reason) {
|
||||
this.ackErrors = error.reason;
|
||||
} else if (error.message){
|
||||
} else if (error.message) {
|
||||
this.ackErrors = error.message;
|
||||
} else {
|
||||
this.ackErrors = 'Something went wrong'
|
||||
console.error(error);
|
||||
}
|
||||
this.ackErrors = 'Something went wrong'
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
change(val){
|
||||
change(val) {
|
||||
this.dirty = true;
|
||||
if (this.hasChangeListener()) this.loading = true;
|
||||
this.$emit('change', val, this.acknowledgeChange);
|
||||
},
|
||||
hasChangeListener(){
|
||||
hasChangeListener() {
|
||||
return this.$listeners && this.$listeners.change;
|
||||
},
|
||||
forceSafeValueUpdate(){
|
||||
forceSafeValueUpdate() {
|
||||
// hack to force the value to update on the child component
|
||||
this.safeValue = null;
|
||||
this.$nextTick(() => this.safeValue = this.value);
|
||||
},
|
||||
focus(){
|
||||
focus() {
|
||||
this.$refs.input.focus();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
errors(){
|
||||
errors() {
|
||||
let errors = this.ackErrors ? [this.ackErrors] : [];
|
||||
if (Array.isArray(this.rulesErrors)){
|
||||
if (Array.isArray(this.rulesErrors)) {
|
||||
errors.push(...this.rulesErrors)
|
||||
}
|
||||
if (Array.isArray(this.errorMessages)){
|
||||
if (Array.isArray(this.errorMessages)) {
|
||||
errors.push(...this.errorMessages);
|
||||
} else if (typeof this.errorMessages === 'string' && this.errorMessages){
|
||||
} else if (typeof this.errorMessages === 'string' && this.errorMessages) {
|
||||
errors.push(this.errorMessages);
|
||||
}
|
||||
return errors;
|
||||
},
|
||||
isDisabled(){
|
||||
isDisabled() {
|
||||
return this.context.editPermission === false || this.disabled;
|
||||
},
|
||||
debounceTime() {
|
||||
if (Number.isFinite(this.debounce)){
|
||||
if (Number.isFinite(this.debounce)) {
|
||||
return this.debounce;
|
||||
} else if (Number.isFinite(this.context.debounceTime)){
|
||||
} else if (Number.isFinite(this.context.debounceTime)) {
|
||||
return this.context.debounceTime;
|
||||
} else {
|
||||
return 750;
|
||||
}
|
||||
},
|
||||
},
|
||||
created(){
|
||||
created() {
|
||||
this.debouncedChange = debounce(this.change, this.debounceTime);
|
||||
},
|
||||
beforeDestroy(){
|
||||
beforeDestroy() {
|
||||
this.debouncedChange.flush();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
};
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -14,15 +14,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
autoGrow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
autoGrow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -77,134 +77,149 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
/**
|
||||
* TreeNode's are list item views of character properties. Every property which
|
||||
* can belong to the character is shown in the tree view of the character
|
||||
* the tree view shows off the full character structure, and where each part of
|
||||
* character comes from.
|
||||
**/
|
||||
import { canBeParent } from '/imports/api/parenting/parenting.js';
|
||||
import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import { some } from 'lodash';
|
||||
/**
|
||||
* TreeNode's are list item views of character properties. Every property which
|
||||
* can belong to the character is shown in the tree view of the character
|
||||
* the tree view shows off the full character structure, and where each part of
|
||||
* character comes from.
|
||||
**/
|
||||
import { canBeParent } from '/imports/api/parenting/parenting.js';
|
||||
import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import { some } from 'lodash';
|
||||
|
||||
export default {
|
||||
name: 'TreeNode',
|
||||
components: {
|
||||
TreeNodeView,
|
||||
export default {
|
||||
name: 'TreeNode',
|
||||
components: {
|
||||
TreeNodeView,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
group: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
organize: Boolean,
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
getChildren: {
|
||||
type: Function,
|
||||
default: undefined,
|
||||
},
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
selected: Boolean,
|
||||
startExpanded: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
|
||||
group: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
organize: Boolean,
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
getChildren: {
|
||||
type: Function,
|
||||
default: undefined,
|
||||
},
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
selected: Boolean,
|
||||
startExpanded: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: this.startExpanded || this.node._ancestorOfMatchedDocument ||
|
||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id) ||
|
||||
false,
|
||||
}},
|
||||
computed: {
|
||||
hasChildren(){
|
||||
return this.children && !!this.children.length || this.lazy && !this.expanded;
|
||||
},
|
||||
showExpanded(){
|
||||
return this.expanded && (this.organize || this.hasChildren)
|
||||
},
|
||||
computedChildren(){
|
||||
let children = [];
|
||||
if (this.children){
|
||||
children.push(...this.children)
|
||||
}
|
||||
if (this.getChildren){
|
||||
children.push(...this.getChildren())
|
||||
}
|
||||
return children;
|
||||
},
|
||||
canExpand(){
|
||||
return canBeParent(this.node.type);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'node._ancestorOfMatchedDocument'(value){
|
||||
this.expanded = !!value ||
|
||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
|
||||
},
|
||||
'selectedNode.ancestors'(value){
|
||||
this.expanded = !!some(value, ref => ref.id === this.node._id) || this.expanded;
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasChildren() {
|
||||
return this.children && !!this.children.length || this.lazy && !this.expanded;
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.components.TreeNodeList = require('./TreeNodeList.vue').default
|
||||
},
|
||||
methods: {
|
||||
icon(type){
|
||||
return getPropertyIcon(type);
|
||||
},
|
||||
}
|
||||
};
|
||||
showExpanded() {
|
||||
return this.expanded && (this.organize || this.hasChildren)
|
||||
},
|
||||
computedChildren() {
|
||||
let children = [];
|
||||
if (this.children) {
|
||||
children.push(...this.children)
|
||||
}
|
||||
if (this.getChildren) {
|
||||
children.push(...this.getChildren())
|
||||
}
|
||||
return children;
|
||||
},
|
||||
canExpand() {
|
||||
return canBeParent(this.node.type);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'node._ancestorOfMatchedDocument'(value) {
|
||||
this.expanded = !!value ||
|
||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
|
||||
},
|
||||
'selectedNode.ancestors'(value) {
|
||||
this.expanded = !!some(value, ref => ref.id === this.node._id) || this.expanded;
|
||||
},
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.components.TreeNodeList = require('./TreeNodeList.vue').default
|
||||
},
|
||||
methods: {
|
||||
icon(type) {
|
||||
return getPropertyIcon(type);
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg) translateZ(0);
|
||||
}
|
||||
.drag-area {
|
||||
box-shadow: -2px 0px 0px 0px #808080;
|
||||
margin-left: 0;
|
||||
min-height: 32px;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
}
|
||||
.empty .drag-area {
|
||||
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
||||
}
|
||||
.empty .v-btn {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.found {
|
||||
background: rgba(200, 0, 0, 0.1) !important;
|
||||
}
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: rgba(251, 0, 0, 0.3);
|
||||
}
|
||||
.v-icon.v-icon--disabled {
|
||||
opacity: 0;
|
||||
}
|
||||
.v-icon {
|
||||
transition: none !important;
|
||||
}
|
||||
.theme--light .tree-node-title:hover {
|
||||
background-color: rgba(0,0,0,.04);
|
||||
}
|
||||
.theme--dark .tree-node-title:hover {
|
||||
background-color: rgba(255,255,255,.04);
|
||||
}
|
||||
.tree-node-title{
|
||||
transition: background ease 0.3s, color ease 0.15s;
|
||||
}
|
||||
.tree-node-title, .dummy-node {
|
||||
height: 40px;
|
||||
}
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg) translateZ(0);
|
||||
}
|
||||
|
||||
.drag-area {
|
||||
box-shadow: -2px 0px 0px 0px #808080;
|
||||
margin-left: 0;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.handle {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.empty .drag-area {
|
||||
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
||||
}
|
||||
|
||||
.empty .v-btn {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.found {
|
||||
background: rgba(200, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: rgba(251, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.v-icon.v-icon--disabled {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.v-icon {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.theme--light .tree-node-title:hover {
|
||||
background-color: rgba(0, 0, 0, .04);
|
||||
}
|
||||
|
||||
.theme--dark .tree-node-title:hover {
|
||||
background-color: rgba(255, 255, 255, .04);
|
||||
}
|
||||
|
||||
.tree-node-title {
|
||||
transition: background ease 0.3s, color ease 0.15s;
|
||||
}
|
||||
|
||||
.tree-node-title,
|
||||
.dummy-node {
|
||||
height: 40px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,102 +33,112 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import draggable from 'vuedraggable';
|
||||
import TreeNode from '/imports/ui/components/tree/TreeNode.vue';
|
||||
import { isParentAllowed } from '/imports/api/parenting/parenting.js';
|
||||
import draggable from 'vuedraggable';
|
||||
import TreeNode from '/imports/ui/components/tree/TreeNode.vue';
|
||||
import { isParentAllowed } from '/imports/api/parenting/parenting.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
TreeNode,
|
||||
},
|
||||
props: {
|
||||
node: Object,
|
||||
group: String,
|
||||
organize: Boolean,
|
||||
lazy: Boolean,
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
ancestorsOfSelectedNode: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
startExpanded: Boolean,
|
||||
},
|
||||
data(){ return {
|
||||
expanded: this.startExpanded || false,
|
||||
export default {
|
||||
components: {
|
||||
draggable,
|
||||
TreeNode,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
group: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
organize: Boolean,
|
||||
lazy: Boolean,
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
ancestorsOfSelectedNode: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
startExpanded: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: this.startExpanded || false,
|
||||
displayedChildren: [],
|
||||
}},
|
||||
computed: {
|
||||
hasChildren(){
|
||||
return this.children && this.children.length;
|
||||
},
|
||||
showExpanded(){
|
||||
return this.expanded && (this.organize || this.hasChildren)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
children(value){
|
||||
this.displayedChildren = value;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasChildren() {
|
||||
return this.children && this.children.length;
|
||||
},
|
||||
showExpanded() {
|
||||
return this.expanded && (this.organize || this.hasChildren)
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
children(value) {
|
||||
this.displayedChildren = value;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.displayedChildren = this.children;
|
||||
},
|
||||
methods: {
|
||||
change({ added, moved }) {
|
||||
let event = moved || added;
|
||||
if (event) {
|
||||
let doc = event.element.node;
|
||||
let newIndex;
|
||||
if (event.newIndex === 0) {
|
||||
newIndex = -0.5;
|
||||
} else {
|
||||
if (event.newIndex < this.children.length) {
|
||||
let childAtNewIndex = this.children[event.newIndex];
|
||||
let indexOrder = childAtNewIndex.node.order;
|
||||
if (event.newIndex > event.oldIndex) {
|
||||
newIndex = indexOrder + 0.5;
|
||||
} else {
|
||||
newIndex = indexOrder - 0.5;
|
||||
}
|
||||
} else {
|
||||
let childBeforeNewIndex = this.children[event.newIndex - 1];
|
||||
newIndex = childBeforeNewIndex.node.order + 0.5;
|
||||
}
|
||||
}
|
||||
if (moved) {
|
||||
this.$emit('reordered', { doc, newIndex });
|
||||
} else if (added) {
|
||||
this.$emit('reorganized', { doc, parent: this.node, newIndex });
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.displayedChildren = this.children;
|
||||
move(evt) {
|
||||
let parentNode = evt.relatedContext.component.$parent.node
|
||||
let parentType = parentNode && parentNode.type || 'root';
|
||||
let childType = evt.draggedContext.element.node.type;
|
||||
let allowed = isParentAllowed({ parentType, childType });
|
||||
return allowed;
|
||||
},
|
||||
methods: {
|
||||
change({added, moved}){
|
||||
let event = moved || added;
|
||||
if (event){
|
||||
let doc = event.element.node;
|
||||
let newIndex;
|
||||
if (event.newIndex === 0){
|
||||
newIndex = -0.5;
|
||||
} else {
|
||||
if (event.newIndex < this.children.length){
|
||||
let childAtNewIndex = this.children[event.newIndex];
|
||||
let indexOrder = childAtNewIndex.node.order;
|
||||
if (event.newIndex > event.oldIndex){
|
||||
newIndex = indexOrder + 0.5;
|
||||
} else {
|
||||
newIndex = indexOrder - 0.5;
|
||||
}
|
||||
} else {
|
||||
let childBeforeNewIndex = this.children[event.newIndex - 1];
|
||||
newIndex = childBeforeNewIndex.node.order + 0.5;
|
||||
}
|
||||
}
|
||||
if (moved){
|
||||
this.$emit('reordered', {doc, newIndex});
|
||||
} else if (added){
|
||||
this.$emit('reorganized', {doc, parent: this.node, newIndex});
|
||||
}
|
||||
}
|
||||
},
|
||||
move(evt){
|
||||
let parentNode = evt.relatedContext.component.$parent.node
|
||||
let parentType = parentNode && parentNode.type || 'root';
|
||||
let childType = evt.draggedContext.element.node.type;
|
||||
let allowed = isParentAllowed({parentType, childType});
|
||||
return allowed;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.flip-list-leave-active {
|
||||
display: none;
|
||||
}
|
||||
.flip-list-move {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
.no-move {
|
||||
transition: transform 0s;
|
||||
}
|
||||
.flip-list-leave-active {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flip-list-move {
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
|
||||
.no-move {
|
||||
transition: transform 0s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -67,25 +67,25 @@
|
||||
:value="model.settings.discordWebhook"
|
||||
@change="(value, ack) => $emit('change', {path: ['settings','discordWebhook'], value, ack})"
|
||||
/>
|
||||
<!--
|
||||
<v-switch
|
||||
label="Use variant encumbrance"
|
||||
:input-value="model.settings.useVariantEncumbrance"
|
||||
:error-messages="errors.useVariantEncumbrance"
|
||||
@change="value => $emit('change', {path: ['settings','useVariantEncumbrance'], value})"
|
||||
/>
|
||||
<v-switch
|
||||
label="Hide spells tab"
|
||||
:input-value="model.settings.hideSpellcasting"
|
||||
:error-messages="errors.hideSpellcasting"
|
||||
@change="value => $emit('change', {path: ['settings','hideSpellcasting'], value})"
|
||||
/>
|
||||
<v-switch
|
||||
label="Swap ability scores and modifiers"
|
||||
:input-value="model.settings.swapStatAndModifier"
|
||||
:error-messages="errors.swapStatAndModifier"
|
||||
@change="value => $emit('change', {path: ['settings','swapStatAndModifier'], value})"
|
||||
/>
|
||||
<!--
|
||||
<v-switch
|
||||
label="Use variant encumbrance"
|
||||
:input-value="model.settings.useVariantEncumbrance"
|
||||
:error-messages="errors.useVariantEncumbrance"
|
||||
@change="value => $emit('change', {path: ['settings','useVariantEncumbrance'], value})"
|
||||
/>
|
||||
<v-switch
|
||||
label="Hide spells tab"
|
||||
:input-value="model.settings.hideSpellcasting"
|
||||
:error-messages="errors.hideSpellcasting"
|
||||
@change="value => $emit('change', {path: ['settings','hideSpellcasting'], value})"
|
||||
/>
|
||||
<v-switch
|
||||
label="Swap ability scores and modifiers"
|
||||
:input-value="model.settings.swapStatAndModifier"
|
||||
:error-messages="errors.swapStatAndModifier"
|
||||
@change="value => $emit('change', {path: ['settings','swapStatAndModifier'], value})"
|
||||
/>
|
||||
-->
|
||||
</form-section>
|
||||
<form-section name="Libraries">
|
||||
@@ -121,41 +121,42 @@
|
||||
|
||||
<script lang="js">
|
||||
import { union, without, debounce } from 'lodash';
|
||||
import FormSection, {FormSections} from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import FormSection, { FormSections } from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import LibraryList from '/imports/ui/library/LibraryList.vue';
|
||||
import LibraryCollections from '/imports/api/library/LibraryCollections.js';
|
||||
import {changeAllowedLibraries, toggleAllUserLibraries} from '/imports/api/creature/creatures/methods/changeAllowedLibraries.js';
|
||||
import { changeAllowedLibraries, toggleAllUserLibraries } from '/imports/api/creature/creatures/methods/changeAllowedLibraries.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormSection,
|
||||
components: {
|
||||
FormSection,
|
||||
FormSections,
|
||||
LibraryList,
|
||||
},
|
||||
props: {
|
||||
stored: {
|
||||
type: Boolean,
|
||||
},
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
attackForm: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
props: {
|
||||
stored: {
|
||||
type: Boolean,
|
||||
},
|
||||
model: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
attackForm: {
|
||||
type: Boolean,
|
||||
},
|
||||
disabled: Boolean,
|
||||
},
|
||||
data() { return {
|
||||
libraryCollections: this.model.allowedLibraryCollections,
|
||||
libraries: this.model.allowedLibraries,
|
||||
libraryWriteLoading: false,
|
||||
libraryWriteError: undefined,
|
||||
dirty: false, // If there are pending changes
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
libraryCollections: this.model.allowedLibraryCollections,
|
||||
libraries: this.model.allowedLibraries,
|
||||
libraryWriteLoading: false,
|
||||
libraryWriteError: undefined,
|
||||
dirty: false, // If there are pending changes
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allUserLibraries() {
|
||||
@@ -211,29 +212,29 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
changeShowTreeTab(value){
|
||||
changeShowTreeTab(value) {
|
||||
this.$emit('change', {
|
||||
path: ['settings','showTreeTab'],
|
||||
path: ['settings', 'showTreeTab'],
|
||||
value: !!value
|
||||
});
|
||||
let currentTab = this.$store.getters.tabById(this.model._id);
|
||||
if (!value && currentTab === 5){
|
||||
if (!value && currentTab === 5) {
|
||||
this.$store.commit(
|
||||
'setTabForCharacterSheet',
|
||||
{id: this.model._id, tab: 4}
|
||||
{ id: this.model._id, tab: 4 }
|
||||
);
|
||||
}
|
||||
},
|
||||
changeHideSpellsTab(value){
|
||||
changeHideSpellsTab(value) {
|
||||
this.$emit('change', {
|
||||
path: ['settings','hideSpellsTab'],
|
||||
path: ['settings', 'hideSpellsTab'],
|
||||
value: !value
|
||||
});
|
||||
let currentTab = this.$store.getters.tabById(this.model._id);
|
||||
if (!value && currentTab === 3){
|
||||
if (!value && currentTab === 3) {
|
||||
this.$store.commit(
|
||||
'setTabForCharacterSheet',
|
||||
{id: this.model._id, tab: 4}
|
||||
{ id: this.model._id, tab: 4 }
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -266,4 +267,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template lang="html">
|
||||
<dialog-base v-if="model" :color="model.color">
|
||||
<dialog-base
|
||||
v-if="model"
|
||||
:color="model.color"
|
||||
>
|
||||
<template slot="toolbar">
|
||||
<v-toolbar-title>
|
||||
Character Details
|
||||
@@ -37,23 +40,23 @@ import { assertEditPermission } from '/imports/api/creature/creatures/creaturePe
|
||||
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
CreatureForm,
|
||||
components: {
|
||||
DialogBase,
|
||||
CreatureForm,
|
||||
ColorPicker,
|
||||
},
|
||||
props: {
|
||||
_id: {
|
||||
},
|
||||
props: {
|
||||
_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
startInEditTab: Boolean,
|
||||
},
|
||||
meteor: {
|
||||
model(){
|
||||
return Creatures.findOne(this._id);
|
||||
},
|
||||
editPermission(){
|
||||
startInEditTab: Boolean,
|
||||
},
|
||||
meteor: {
|
||||
model() {
|
||||
return Creatures.findOne(this._id);
|
||||
},
|
||||
editPermission() {
|
||||
try {
|
||||
assertEditPermission(this.model, Meteor.userId());
|
||||
return true;
|
||||
@@ -61,12 +64,12 @@ export default {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
change({path, value, ack}){
|
||||
updateCreature.call({_id: this._id, path, value}, (error) =>{
|
||||
if (error){
|
||||
if(ack){
|
||||
},
|
||||
methods: {
|
||||
change({ path, value, ack }) {
|
||||
updateCreature.call({ _id: this._id, path, value }, (error) => {
|
||||
if (error) {
|
||||
if (ack) {
|
||||
ack(error && error.reason || error)
|
||||
} else {
|
||||
console.error(error)
|
||||
@@ -74,11 +77,12 @@ export default {
|
||||
} else if (ack) {
|
||||
ack();
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -12,24 +12,26 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import BuildTreeNode from '/imports/ui/creature/buildTree/BuildTreeNode.vue';
|
||||
import BuildTreeNode from '/imports/ui/creature/buildTree/BuildTreeNode.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BuildTreeNode,
|
||||
export default {
|
||||
components: {
|
||||
BuildTreeNode,
|
||||
},
|
||||
props: {
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
props: {
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
parentSlotId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
parentSlotId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
data(){ return {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expanded: false,
|
||||
}},
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -37,43 +37,46 @@ import removeCreature from '/imports/api/creature/creatures/methods/removeCreatu
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
},
|
||||
props: {
|
||||
id: String,
|
||||
},
|
||||
data(){return {
|
||||
inputName: undefined,
|
||||
}},
|
||||
computed: {
|
||||
nameMatch(){
|
||||
if (!this.name) return true;
|
||||
let uppername = this.name.toUpperCase();
|
||||
let upperInputName = this.inputName && this.inputName.toUpperCase();
|
||||
return uppername === upperInputName;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
name(){
|
||||
let creature = Creatures.findOne(this.id, {fields: {name: 1}});
|
||||
return creature && creature.name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
remove(){
|
||||
components: {
|
||||
DialogBase,
|
||||
},
|
||||
props: {
|
||||
id: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputName: undefined,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nameMatch() {
|
||||
if (!this.name) return true;
|
||||
let uppername = this.name.toUpperCase();
|
||||
let upperInputName = this.inputName && this.inputName.toUpperCase();
|
||||
return uppername === upperInputName;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
name() {
|
||||
let creature = Creatures.findOne(this.id, { fields: { name: 1 } });
|
||||
return creature && creature.name;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
remove() {
|
||||
this.$router.push('/characterList');
|
||||
this.$store.dispatch('popDialogStack');
|
||||
removeCreature.call({charId: this.id}, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
snackbar({text: error.message || error.toString()});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
removeCreature.call({ charId: this.id }, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
snackbar({ text: error.message || error.toString() });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
size="64"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!creature"
|
||||
>
|
||||
<div v-else-if="!creature">
|
||||
<v-layout
|
||||
column
|
||||
align-center
|
||||
@@ -55,9 +53,7 @@
|
||||
<v-tab-item>
|
||||
<inventory-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item
|
||||
v-if="!creature.settings.hideSpellsTab"
|
||||
>
|
||||
<v-tab-item v-if="!creature.settings.hideSpellsTab">
|
||||
<spells-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
@@ -66,9 +62,7 @@
|
||||
<v-tab-item>
|
||||
<build-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item
|
||||
v-if="creature.settings.showTreeTab"
|
||||
>
|
||||
<v-tab-item v-if="creature.settings.showTreeTab">
|
||||
<tree-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
@@ -78,108 +72,108 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
//TODO add a "no character found" screen if shown on a false address
|
||||
// or on a character the user does not have permission to view
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import StatsTab from '/imports/ui/creature/character/characterSheetTabs/StatsTab.vue';
|
||||
import FeaturesTab from '/imports/ui/creature/character/characterSheetTabs/FeaturesTab.vue';
|
||||
import InventoryTab from '/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue';
|
||||
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
|
||||
import CharacterTab from '/imports/ui/creature/character/characterSheetTabs/JournalTab.vue';
|
||||
import BuildTab from '/imports/ui/creature/character/characterSheetTabs/BuildTab.vue';
|
||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
//TODO add a "no character found" screen if shown on a false address
|
||||
// or on a character the user does not have permission to view
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import StatsTab from '/imports/ui/creature/character/characterSheetTabs/StatsTab.vue';
|
||||
import FeaturesTab from '/imports/ui/creature/character/characterSheetTabs/FeaturesTab.vue';
|
||||
import InventoryTab from '/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue';
|
||||
import SpellsTab from '/imports/ui/creature/character/characterSheetTabs/SpellsTab.vue';
|
||||
import CharacterTab from '/imports/ui/creature/character/characterSheetTabs/JournalTab.vue';
|
||||
import BuildTab from '/imports/ui/creature/character/characterSheetTabs/BuildTab.vue';
|
||||
import TreeTab from '/imports/ui/creature/character/characterSheetTabs/TreeTab.vue';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StatsTab,
|
||||
FeaturesTab,
|
||||
InventoryTab,
|
||||
SpellsTab,
|
||||
CharacterTab,
|
||||
BuildTab,
|
||||
TreeTab,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['creatureId', 'editPermission'],
|
||||
export default {
|
||||
components: {
|
||||
StatsTab,
|
||||
FeaturesTab,
|
||||
InventoryTab,
|
||||
SpellsTab,
|
||||
CharacterTab,
|
||||
BuildTab,
|
||||
TreeTab,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
activeTab: {
|
||||
get(){
|
||||
return this.tabs;
|
||||
},
|
||||
set(newTab){
|
||||
this.$emit('update:tabs', newTab);
|
||||
},
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['creatureId', 'editPermission'],
|
||||
},
|
||||
computed: {
|
||||
activeTab: {
|
||||
get() {
|
||||
return this.tabs;
|
||||
},
|
||||
set(newTab) {
|
||||
this.$emit('update:tabs', newTab);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'creature.name'(value){
|
||||
this.$store.commit('setPageTitle', value || 'Character Sheet');
|
||||
},
|
||||
watch: {
|
||||
'creature.name'(value) {
|
||||
this.$store.commit('setPageTitle', value || 'Character Sheet');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.commit('setPageTitle', this.creature && this.creature.name || 'Character Sheet');
|
||||
this.nameObserver = Creatures.find({
|
||||
creatureId: this.creatureId,
|
||||
}, {
|
||||
fields: { name: 1 },
|
||||
}).observe({
|
||||
added: ({ name }) =>
|
||||
this.$store.commit('setPageTitle', name || 'Character Sheet'),
|
||||
changed: ({ name }) =>
|
||||
this.$store.commit('setPageTitle', name || 'Character Sheet'),
|
||||
});
|
||||
let that = this;
|
||||
this.logObserver = CreatureLogs.find({
|
||||
creatureId: this.creatureId,
|
||||
}).observe({
|
||||
added({ content }) {
|
||||
if (!that.$subReady.singleCharacter) return;
|
||||
if (that.$store.state.rightDrawer) return;
|
||||
snackbar({ content });
|
||||
},
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.nameObserver.stop();
|
||||
this.logObserver.stop();
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'singleCharacter'() {
|
||||
return [this.creatureId];
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
this.$store.commit('setPageTitle', this.creature && this.creature.name || 'Character Sheet');
|
||||
this.nameObserver = Creatures.find({
|
||||
creatureId: this.creatureId,
|
||||
}, {
|
||||
fields: {name: 1},
|
||||
}).observe({
|
||||
added: ({name}) =>
|
||||
this.$store.commit('setPageTitle', name || 'Character Sheet'),
|
||||
changed: ({ name }) =>
|
||||
this.$store.commit('setPageTitle', name || 'Character Sheet'),
|
||||
});
|
||||
let that = this;
|
||||
this.logObserver = CreatureLogs.find({
|
||||
creatureId: this.creatureId,
|
||||
}).observe({
|
||||
added({content}){
|
||||
if (!that.$subReady.singleCharacter) return;
|
||||
if (that.$store.state.rightDrawer) return;
|
||||
snackbar({content});
|
||||
},
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, {
|
||||
fields: { variables: 0 }
|
||||
});
|
||||
},
|
||||
beforeDestroy(){
|
||||
this.nameObserver.stop();
|
||||
this.logObserver.stop();
|
||||
editPermission() {
|
||||
try {
|
||||
assertEditPermission(this.creature, Meteor.userId());
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'singleCharacter'(){
|
||||
return [this.creatureId];
|
||||
},
|
||||
},
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId, {
|
||||
fields: {variables: 0}
|
||||
});
|
||||
},
|
||||
editPermission(){
|
||||
try {
|
||||
assertEditPermission(this.creature, Meteor.userId());
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.character-sheet .v-window-item {
|
||||
min-height: calc(100vh - 96px);
|
||||
overflow: hidden;
|
||||
}
|
||||
.character-sheet .v-window-item {
|
||||
min-height: calc(100vh - 96px);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,17 +11,13 @@
|
||||
dense
|
||||
>
|
||||
<v-app-bar-nav-icon @click="toggleDrawer" />
|
||||
<v-fade-transition
|
||||
mode="out-in"
|
||||
>
|
||||
<v-fade-transition mode="out-in">
|
||||
<v-toolbar-title :key="$store.state.pageTitle">
|
||||
{{ $store.state.pageTitle }}
|
||||
</v-toolbar-title>
|
||||
</v-fade-transition>
|
||||
<v-spacer />
|
||||
<v-fade-transition
|
||||
mode="out-in"
|
||||
>
|
||||
<v-fade-transition mode="out-in">
|
||||
<v-layout
|
||||
:key="$route.meta.title"
|
||||
class="flex-shrink-0 flex-grow-0"
|
||||
@@ -151,17 +147,17 @@ export default {
|
||||
context: { default: {} }
|
||||
},
|
||||
computed: {
|
||||
creatureId(){
|
||||
creatureId() {
|
||||
return this.$route.params.id;
|
||||
},
|
||||
toolbarColor(){
|
||||
if (this.creature && this.creature.color){
|
||||
toolbarColor() {
|
||||
if (this.creature && this.creature.color) {
|
||||
return this.creature.color;
|
||||
} else {
|
||||
return getThemeColor('secondary');
|
||||
}
|
||||
},
|
||||
isDark(){
|
||||
isDark() {
|
||||
return isDarkColor(this.toolbarColor);
|
||||
},
|
||||
},
|
||||
@@ -170,49 +166,49 @@ export default {
|
||||
'toggleDrawer',
|
||||
'toggleRightDrawer',
|
||||
]),
|
||||
showCharacterForm(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-form-dialog',
|
||||
elementId: 'creature-menu',
|
||||
data: {
|
||||
_id: this.creatureId,
|
||||
},
|
||||
});
|
||||
},
|
||||
showShareDialog(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'share-dialog',
|
||||
elementId: 'creature-menu',
|
||||
data: {
|
||||
docRef: {
|
||||
id: this.creatureId,
|
||||
collection: 'creatures',
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
deleteCharacter(){
|
||||
let that = this;
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'delete-confirmation-dialog',
|
||||
elementId: 'creature-menu',
|
||||
data: {
|
||||
name: this.creature.name,
|
||||
typeName: 'Character'
|
||||
},
|
||||
callback(confirmation){
|
||||
if(!confirmation) return;
|
||||
removeCreature.call({charId: that.creatureId}, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
} else {
|
||||
that.$router.push('/characterList');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
unshareWithMe(){
|
||||
showCharacterForm() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-form-dialog',
|
||||
elementId: 'creature-menu',
|
||||
data: {
|
||||
_id: this.creatureId,
|
||||
},
|
||||
});
|
||||
},
|
||||
showShareDialog() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'share-dialog',
|
||||
elementId: 'creature-menu',
|
||||
data: {
|
||||
docRef: {
|
||||
id: this.creatureId,
|
||||
collection: 'creatures',
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
deleteCharacter() {
|
||||
let that = this;
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'delete-confirmation-dialog',
|
||||
elementId: 'creature-menu',
|
||||
data: {
|
||||
name: this.creature.name,
|
||||
typeName: 'Character'
|
||||
},
|
||||
callback(confirmation) {
|
||||
if (!confirmation) return;
|
||||
removeCreature.call({ charId: that.creatureId }, (error) => {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
} else {
|
||||
that.$router.push('/characterList');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
unshareWithMe() {
|
||||
updateUserSharePermissions.call({
|
||||
docRef: {
|
||||
collection: 'creatures',
|
||||
@@ -230,10 +226,10 @@ export default {
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
creature(){
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId);
|
||||
},
|
||||
editPermission(){
|
||||
editPermission() {
|
||||
try {
|
||||
assertEditPermission(this.creature, Meteor.userId());
|
||||
return true;
|
||||
@@ -249,9 +245,11 @@ export default {
|
||||
.character-sheet-toolbar .v-tabs__container--grow .v-tabs__div {
|
||||
max-width: 120px !important;
|
||||
}
|
||||
|
||||
.character-sheet-toolbar .v-tabs__bar {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.character-sheet-fab {
|
||||
bottom: -24px;
|
||||
right: 8px;
|
||||
|
||||
@@ -16,44 +16,45 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import FeatureCard from '/imports/ui/properties/components/features/FeatureCard.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import FeatureCard from '/imports/ui/properties/components/features/FeatureCard.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
FeatureCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
features(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'feature',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
featureClicked({_id}){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `${_id}`,
|
||||
data: {_id},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
FeatureCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
features() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'feature',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
featureClicked({ _id }) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -30,9 +30,7 @@
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-list-item-title>
|
||||
<coin-value
|
||||
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
|
||||
/>
|
||||
<coin-value :value="variables && variables.valueTotal && variables.valueTotal.value|| 0" />
|
||||
</v-list-item-title>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
@@ -85,9 +83,7 @@
|
||||
v-for="container in containersWithoutAncestorContainers"
|
||||
:key="container._id"
|
||||
>
|
||||
<container-card
|
||||
:model="container"
|
||||
/>
|
||||
<container-card :model="container" />
|
||||
</div>
|
||||
</column-layout>
|
||||
</div>
|
||||
@@ -107,83 +103,87 @@ import stripFloatingPointOddities from '/imports/api/engine/computation/utility/
|
||||
import CreatureVariables from '../../../../api/creature/creatures/CreatureVariables';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
ContainerCard,
|
||||
components: {
|
||||
ColumnLayout,
|
||||
ContainerCard,
|
||||
ToolbarCard,
|
||||
ItemList,
|
||||
CoinValue,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
organize: false,
|
||||
}},
|
||||
meteor: {
|
||||
containers(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'container',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
},
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId, {fields: {
|
||||
color: 1,
|
||||
variables: 1,
|
||||
}});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
organize: false,
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
containers() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'container',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, {
|
||||
fields: {
|
||||
color: 1,
|
||||
variables: 1,
|
||||
}
|
||||
});
|
||||
},
|
||||
variables() {
|
||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||
},
|
||||
containersWithoutAncestorContainers(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.containerIds
|
||||
},
|
||||
type: 'container',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
},
|
||||
carriedItems(){
|
||||
containersWithoutAncestorContainers() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.containerIds
|
||||
},
|
||||
type: 'item',
|
||||
equipped: {$ne: true},
|
||||
removed: {$ne: true},
|
||||
deactivatedByAncestor: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
$eq: this.creatureId,
|
||||
$nin: this.containerIds
|
||||
},
|
||||
type: 'container',
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
equippedItems(){
|
||||
carriedItems() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
},
|
||||
type: 'item',
|
||||
$eq: this.creatureId,
|
||||
$nin: this.containerIds
|
||||
},
|
||||
type: 'item',
|
||||
equipped: { $ne: true },
|
||||
removed: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
equippedItems() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
},
|
||||
type: 'item',
|
||||
equipped: true,
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1},
|
||||
});
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
});
|
||||
},
|
||||
equipmentParentRef(){
|
||||
equipmentParentRef() {
|
||||
return getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.equipment
|
||||
) || getParentRefByTag(
|
||||
@@ -193,7 +193,7 @@ export default {
|
||||
collection: 'creatures'
|
||||
};
|
||||
},
|
||||
carriedParentRef(){
|
||||
carriedParentRef() {
|
||||
return getParentRefByTag(
|
||||
this.creatureId, BUILT_IN_TAGS.carried
|
||||
) || getParentRefByTag(
|
||||
@@ -203,30 +203,31 @@ export default {
|
||||
collection: 'creatures'
|
||||
};
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
containerIds(){
|
||||
return this.containers.map(container => container._id);
|
||||
},
|
||||
weightCarried(){
|
||||
},
|
||||
computed: {
|
||||
containerIds() {
|
||||
return this.containers.map(container => container._id);
|
||||
},
|
||||
weightCarried() {
|
||||
return stripFloatingPointOddities(
|
||||
this.variables &&
|
||||
this.variables.weightCarried &&
|
||||
this.variables.weightCarried.value || 0
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty(_id){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `tree-node-${_id}`,
|
||||
data: {_id},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty(_id) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `tree-node-${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -29,41 +29,43 @@ import SpellListCard from '/imports/ui/properties/components/spells/SpellListCar
|
||||
import SpellList from '/imports/ui/properties/components/spells/SpellList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ColumnLayout,
|
||||
components: {
|
||||
ColumnLayout,
|
||||
SpellList,
|
||||
SpellListCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
SpellListCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
data(){ return {
|
||||
organize: false,
|
||||
}},
|
||||
meteor: {
|
||||
spellLists(){
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
organize: false,
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
spellLists() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'spellList',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
spellsWithoutList(){
|
||||
},
|
||||
spellsWithoutList() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.spellListIds,
|
||||
},
|
||||
type: 'spell',
|
||||
removed: {$ne: true},
|
||||
deactivatedByAncestor: {$ne: true},
|
||||
deactivatedByToggle: {$ne: true},
|
||||
removed: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
deactivatedByToggle: { $ne: true },
|
||||
}, {
|
||||
sort: {
|
||||
level: 1,
|
||||
@@ -71,36 +73,37 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
spellListsWithoutAncestorSpellLists(){
|
||||
spellListsWithoutAncestorSpellLists() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': {
|
||||
$eq: this.creatureId,
|
||||
$nin: this.spellListIds,
|
||||
},
|
||||
type: 'spellList',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
spellListIds(){
|
||||
return this.spellLists.map(spellList => spellList._id);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty(_id){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `spell-list-tile-${_id}`,
|
||||
data: {_id},
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
spellListIds() {
|
||||
return this.spellLists.map(spellList => spellList._id);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty(_id) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `spell-list-tile-${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="stats-tab ma-2"
|
||||
>
|
||||
<div class="stats-tab ma-2">
|
||||
<health-bar-card-container :creature-id="creatureId" />
|
||||
|
||||
<column-layout>
|
||||
@@ -171,9 +169,7 @@
|
||||
v-if="spellSlots && spellSlots.length || hasSpells"
|
||||
class="spell-slots"
|
||||
>
|
||||
<v-card
|
||||
data-id="spell-slot-card"
|
||||
>
|
||||
<v-card data-id="spell-slot-card">
|
||||
<v-list
|
||||
v-if="spellSlots && spellSlots.length"
|
||||
two-line
|
||||
@@ -351,216 +347,219 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
||||
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import DamageMultiplierCard from '/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue';
|
||||
import HealthBarCardContainer from '/imports/ui/properties/components/attributes/HealthBarCardContainer.vue';
|
||||
import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue';
|
||||
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
||||
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
||||
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
|
||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
const getProperties = function(creature, filter, options = {
|
||||
sort: {order: 1}
|
||||
}){
|
||||
if (!creature) return;
|
||||
if (creature.settings.hideUnusedStats){
|
||||
filter.hide = {$ne: true};
|
||||
}
|
||||
filter['ancestors.id'] = creature._id;
|
||||
filter.removed = {$ne: true};
|
||||
filter.inactive = {$ne: true};
|
||||
filter.overridden = {$ne: true};
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
|
||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import AttributeCard from '/imports/ui/properties/components/attributes/AttributeCard.vue';
|
||||
import AbilityListTile from '/imports/ui/properties/components/attributes/AbilityListTile.vue';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import DamageMultiplierCard from '/imports/ui/properties/components/damageMultipliers/DamageMultiplierCard.vue';
|
||||
import HealthBarCardContainer from '/imports/ui/properties/components/attributes/HealthBarCardContainer.vue';
|
||||
import HitDiceListTile from '/imports/ui/properties/components/attributes/HitDiceListTile.vue';
|
||||
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
||||
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
||||
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
|
||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
return CreatureProperties.find(filter, options);
|
||||
};
|
||||
|
||||
const getAttributeOfType = function(creature, type){
|
||||
return getProperties(creature, {
|
||||
type: 'attribute',
|
||||
attributeType: type,
|
||||
});
|
||||
};
|
||||
|
||||
const getSkillOfType = function(creature, type){
|
||||
return getProperties(creature, {
|
||||
type: 'skill',
|
||||
skillType: type,
|
||||
});
|
||||
const getProperties = function (creature, filter, options = {
|
||||
sort: { order: 1 }
|
||||
}) {
|
||||
if (!creature) return;
|
||||
if (creature.settings.hideUnusedStats) {
|
||||
filter.hide = { $ne: true };
|
||||
}
|
||||
filter['ancestors.id'] = creature._id;
|
||||
filter.removed = { $ne: true };
|
||||
filter.inactive = { $ne: true };
|
||||
filter.overridden = { $ne: true };
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RestButton,
|
||||
AbilityListTile,
|
||||
AttributeCard,
|
||||
ColumnLayout,
|
||||
DamageMultiplierCard,
|
||||
HealthBarCardContainer,
|
||||
HitDiceListTile,
|
||||
SkillListTile,
|
||||
ResourceCard,
|
||||
SpellSlotListTile,
|
||||
ActionCard,
|
||||
ToggleCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
return CreatureProperties.find(filter, options);
|
||||
};
|
||||
|
||||
const getAttributeOfType = function (creature, type) {
|
||||
return getProperties(creature, {
|
||||
type: 'attribute',
|
||||
attributeType: type,
|
||||
});
|
||||
};
|
||||
|
||||
const getSkillOfType = function (creature, type) {
|
||||
return getProperties(creature, {
|
||||
type: 'skill',
|
||||
skillType: type,
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RestButton,
|
||||
AbilityListTile,
|
||||
AttributeCard,
|
||||
ColumnLayout,
|
||||
DamageMultiplierCard,
|
||||
HealthBarCardContainer,
|
||||
HitDiceListTile,
|
||||
SkillListTile,
|
||||
ResourceCard,
|
||||
SpellSlotListTile,
|
||||
ActionCard,
|
||||
ToggleCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
doCheckLoading: false,
|
||||
}},
|
||||
meteor: {
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId, {fields: {settings: 1}});
|
||||
},
|
||||
abilities(){
|
||||
return getAttributeOfType(this.creature, 'ability');
|
||||
},
|
||||
stats(){
|
||||
return getAttributeOfType(this.creature, 'stat');
|
||||
},
|
||||
toggles(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'toggle',
|
||||
removed: {$ne: true},
|
||||
deactivatedByAncestor: {$ne: true},
|
||||
showUI: true,
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
creature() {
|
||||
return Creatures.findOne(this.creatureId, { fields: { settings: 1 } });
|
||||
},
|
||||
abilities() {
|
||||
return getAttributeOfType(this.creature, 'ability');
|
||||
},
|
||||
stats() {
|
||||
return getAttributeOfType(this.creature, 'stat');
|
||||
},
|
||||
toggles() {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'toggle',
|
||||
removed: { $ne: true },
|
||||
deactivatedByAncestor: { $ne: true },
|
||||
showUI: true,
|
||||
}, {
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
modifiers() {
|
||||
return getAttributeOfType(this.creature, 'modifier');
|
||||
},
|
||||
resources() {
|
||||
return getAttributeOfType(this.creature, 'resource');
|
||||
},
|
||||
spellSlots() {
|
||||
return getAttributeOfType(this.creature, 'spellSlot');
|
||||
},
|
||||
hasSpells() {
|
||||
const cursor = getProperties(this.creature, {
|
||||
type: 'spell',
|
||||
})
|
||||
return cursor && cursor.count();
|
||||
},
|
||||
hitDice() {
|
||||
return getAttributeOfType(this.creature, 'hitDice');
|
||||
},
|
||||
checks() {
|
||||
return getSkillOfType(this.creature, 'check');
|
||||
},
|
||||
savingThrows() {
|
||||
return getSkillOfType(this.creature, 'save');
|
||||
},
|
||||
skills() {
|
||||
return getSkillOfType(this.creature, 'skill');
|
||||
},
|
||||
tools() {
|
||||
return getSkillOfType(this.creature, 'tool');
|
||||
},
|
||||
weapons() {
|
||||
return getSkillOfType(this.creature, 'weapon');
|
||||
},
|
||||
armors() {
|
||||
return getSkillOfType(this.creature, 'armor');
|
||||
},
|
||||
languages() {
|
||||
return getSkillOfType(this.creature, 'language');
|
||||
},
|
||||
actions() {
|
||||
return getProperties(this.creature, { type: 'action' });
|
||||
},
|
||||
appliedBuffs() {
|
||||
return getProperties(this.creature, { type: 'buff' });
|
||||
},
|
||||
multipliers() {
|
||||
return getProperties(this.creature, {
|
||||
type: 'damageMultiplier'
|
||||
}, {
|
||||
sort: { value: 1, order: 1 }
|
||||
});
|
||||
},
|
||||
attacks() {
|
||||
let props = getProperties(this.creature, { type: 'attack' })
|
||||
return props && props.map(attack => {
|
||||
attack.children = CreatureProperties.find({
|
||||
'ancestors.id': attack._id,
|
||||
removed: { $ne: true },
|
||||
inactive: { $ne: true },
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
sort: { order: 1 }
|
||||
});
|
||||
},
|
||||
modifiers(){
|
||||
return getAttributeOfType(this.creature, 'modifier');
|
||||
},
|
||||
resources(){
|
||||
return getAttributeOfType(this.creature, 'resource');
|
||||
},
|
||||
spellSlots(){
|
||||
return getAttributeOfType(this.creature, 'spellSlot');
|
||||
},
|
||||
hasSpells(){
|
||||
const cursor = getProperties(this.creature, {
|
||||
type: 'spell',
|
||||
})
|
||||
return cursor && cursor.count();
|
||||
},
|
||||
hitDice(){
|
||||
return getAttributeOfType(this.creature, 'hitDice');
|
||||
},
|
||||
checks(){
|
||||
return getSkillOfType(this.creature, 'check');
|
||||
},
|
||||
savingThrows(){
|
||||
return getSkillOfType(this.creature, 'save');
|
||||
},
|
||||
skills(){
|
||||
return getSkillOfType(this.creature, 'skill');
|
||||
},
|
||||
tools(){
|
||||
return getSkillOfType(this.creature, 'tool');
|
||||
},
|
||||
weapons(){
|
||||
return getSkillOfType(this.creature, 'weapon');
|
||||
},
|
||||
armors(){
|
||||
return getSkillOfType(this.creature, 'armor');
|
||||
},
|
||||
languages(){
|
||||
return getSkillOfType(this.creature, 'language');
|
||||
},
|
||||
actions(){
|
||||
return getProperties(this.creature, {type: 'action'});
|
||||
},
|
||||
appliedBuffs(){
|
||||
return getProperties(this.creature, {type: 'buff'});
|
||||
},
|
||||
multipliers(){
|
||||
return getProperties(this.creature, {
|
||||
type: 'damageMultiplier'
|
||||
}, {
|
||||
sort: {value: 1, order: 1}
|
||||
});
|
||||
},
|
||||
attacks(){
|
||||
let props = getProperties(this.creature, {type: 'attack'})
|
||||
return props && props.map(attack => {
|
||||
attack.children = CreatureProperties.find({
|
||||
'ancestors.id': attack._id,
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
return attack;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty({_id}){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `${_id}`,
|
||||
data: {_id},
|
||||
});
|
||||
},
|
||||
clickTreeProperty({_id}){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `tree-node-${_id}`,
|
||||
data: {_id},
|
||||
});
|
||||
},
|
||||
incrementChange(_id, {type, value}){
|
||||
if (type === 'increment'){
|
||||
damageProperty.call({_id, operation: 'increment' ,value: -value});
|
||||
}
|
||||
},
|
||||
softRemove(_id){
|
||||
softRemoveProperty.call({_id}, error => {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
},
|
||||
castSpell(){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'cast-spell-with-slot-dialog',
|
||||
elementId: 'spell-slot-card',
|
||||
data: {
|
||||
creatureId: this.creatureId,
|
||||
},
|
||||
callback({spellId, slotId, advantage} = {}){
|
||||
if (!spellId) return;
|
||||
doCastSpell.call({
|
||||
spellId,
|
||||
slotId,
|
||||
scope: {
|
||||
$attackAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
if (!error) return;
|
||||
snackbar({text: error.reason || error.message || error.toString()});
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
return attack;
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickProperty({ _id }) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
clickTreeProperty({ _id }) {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `tree-node-${_id}`,
|
||||
data: { _id },
|
||||
});
|
||||
},
|
||||
incrementChange(_id, { type, value }) {
|
||||
if (type === 'increment') {
|
||||
damageProperty.call({ _id, operation: 'increment', value: -value });
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
softRemove(_id) {
|
||||
softRemoveProperty.call({ _id }, error => {
|
||||
if (error) console.error(error);
|
||||
});
|
||||
},
|
||||
castSpell() {
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'cast-spell-with-slot-dialog',
|
||||
elementId: 'spell-slot-card',
|
||||
data: {
|
||||
creatureId: this.creatureId,
|
||||
},
|
||||
callback({ spellId, slotId, advantage } = {}) {
|
||||
if (!spellId) return;
|
||||
doCastSpell.call({
|
||||
spellId,
|
||||
slotId,
|
||||
scope: {
|
||||
$attackAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
if (!error) return;
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -13,80 +13,81 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import nodesToTree from '/imports/api/parenting/nodesToTree.js'
|
||||
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
|
||||
import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import nodesToTree from '/imports/api/parenting/nodesToTree.js'
|
||||
import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
|
||||
import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeList,
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeList,
|
||||
},
|
||||
props: {
|
||||
root: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
props: {
|
||||
root: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
organize: Boolean,
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
group: {
|
||||
type: String,
|
||||
default: 'creatureProperties'
|
||||
},
|
||||
expanded: Boolean,
|
||||
organize: Boolean,
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
meteor: {
|
||||
children(){
|
||||
const children = nodesToTree({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: this.root.id,
|
||||
filter: this.filter,
|
||||
includeFilteredDocAncestors: true,
|
||||
includeFilteredDocDescendants: true,
|
||||
});
|
||||
this.$emit('length', children.length);
|
||||
return children;
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
methods: {
|
||||
reordered({doc, newIndex}){
|
||||
reorderDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
reorganized({doc, parent, newIndex}){
|
||||
let parentRef;
|
||||
if (parent){
|
||||
parentRef = {
|
||||
id: parent._id,
|
||||
collection: 'creatureProperties',
|
||||
};
|
||||
} else {
|
||||
parentRef = this.root;
|
||||
}
|
||||
organizeDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
parentRef,
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
group: {
|
||||
type: String,
|
||||
default: 'creatureProperties'
|
||||
},
|
||||
};
|
||||
expanded: Boolean,
|
||||
},
|
||||
meteor: {
|
||||
children() {
|
||||
const children = nodesToTree({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: this.root.id,
|
||||
filter: this.filter,
|
||||
includeFilteredDocAncestors: true,
|
||||
includeFilteredDocDescendants: true,
|
||||
});
|
||||
this.$emit('length', children.length);
|
||||
return children;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reordered({ doc, newIndex }) {
|
||||
reorderDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
reorganized({ doc, parent, newIndex }) {
|
||||
let parentRef;
|
||||
if (parent) {
|
||||
parentRef = {
|
||||
id: parent._id,
|
||||
collection: 'creatureProperties',
|
||||
};
|
||||
} else {
|
||||
parentRef = this.root;
|
||||
}
|
||||
organizeDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
parentRef,
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,7 @@ import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
export default {
|
||||
components: {
|
||||
SelectablePropertyDialog,
|
||||
CreaturePropertyInsertForm,
|
||||
CreaturePropertyInsertForm,
|
||||
},
|
||||
props: {
|
||||
forcedType: {
|
||||
@@ -28,21 +28,24 @@ export default {
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() { return {
|
||||
type: undefined,
|
||||
};},
|
||||
methods: {
|
||||
getPropertyName,
|
||||
back(){
|
||||
if (this.forcedType){
|
||||
data() {
|
||||
return {
|
||||
type: undefined,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getPropertyName,
|
||||
back() {
|
||||
if (this.forcedType) {
|
||||
this.$store.dispatch('popDialogStack');
|
||||
} else {
|
||||
this.type = undefined;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user