Applied style rules to genocide all \t characters

This commit is contained in:
Stefan Zermatten
2022-10-09 16:01:36 +02:00
parent de598c70a7
commit 2fa913b09a
208 changed files with 6027 additions and 5801 deletions

View File

@@ -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>

View File

@@ -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);

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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({

View File

@@ -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: {

View File

@@ -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' },
});
},
});

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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');
}

View File

@@ -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;

View File

@@ -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 },
});
},
});

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -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 };

View File

@@ -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,
});
}

View File

@@ -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 },
});
}
},

View File

@@ -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,
}
});
},
});

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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`
});

View File

@@ -1,4 +1,4 @@
import { pick } from "lodash";
import { pick } from 'lodash';
export default function aggregateEffect({node, linkedNode, link}){
if (link.data !== 'effect') return;

View File

@@ -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: {

View File

@@ -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 };

View File

@@ -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,
};

View File

@@ -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({

View File

@@ -22,7 +22,7 @@ let ChildSchema = new SimpleSchema({
order: {
type: Number,
},
parent: {
parent: {
type: RefSchema,
optional: true,
},

View File

@@ -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,

View File

@@ -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'],

View File

@@ -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

View File

@@ -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,

View File

@@ -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();
}
},

View File

@@ -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 };

View File

@@ -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,

View File

@@ -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({});

View File

@@ -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()

View File

@@ -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: {

View File

@@ -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

View File

@@ -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({

View File

@@ -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 };

View File

@@ -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({

View File

@@ -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({});

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
},

View File

@@ -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 };

View File

@@ -41,7 +41,7 @@ const ComputedOnlyToggleSchema = createPropertySchema({
});
const ComputedToggleSchema = new SimpleSchema()
.extend(ComputedOnlyToggleSchema)
.extend(ToggleSchema);
.extend(ComputedOnlyToggleSchema)
.extend(ToggleSchema);
export { ToggleSchema, ComputedOnlyToggleSchema, ComputedToggleSchema };

View File

@@ -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;

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 };

View File

@@ -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;
},

View File

@@ -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,
});
},
});

View File

@@ -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;
},

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}
});

View File

@@ -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;

View File

@@ -10,7 +10,7 @@ const dbVersionToGitVersion = {
const getVersion = new ValidatedMethod({
name: 'admin.getVersion',
validate: null,
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,

View File

@@ -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);

View File

@@ -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());
}
});

View File

@@ -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]+)?/,

View File

@@ -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);
}

View File

@@ -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({

View File

@@ -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();
},

View File

@@ -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: {

View File

@@ -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 },
});
});

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
},
};

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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