Compare commits
23 Commits
2.0-beta.3
...
2.0-beta.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f043c41e12 | ||
|
|
0277de76c4 | ||
|
|
ffc3211ff9 | ||
|
|
8162c76185 | ||
|
|
e21586e9ce | ||
|
|
4c2155d8ff | ||
|
|
44cc46ed22 | ||
|
|
d2b5d5f01d | ||
|
|
4492c47b00 | ||
|
|
6600cea9fa | ||
|
|
741a9b080a | ||
|
|
b041db22e4 | ||
|
|
a465e2ce87 | ||
|
|
8ecefb03ad | ||
|
|
9f62a78eb0 | ||
|
|
16e2b1249f | ||
|
|
a35f9221a2 | ||
|
|
6364549d50 | ||
|
|
d999fb46a7 | ||
|
|
ec01a2adb5 | ||
|
|
1f64558100 | ||
|
|
19a2798bf7 | ||
|
|
a5f2c2e0d2 |
@@ -9,6 +9,8 @@ import { recomputeCreatureByDoc } from '/imports/api/creature/computation/method
|
||||
import { doActionWork } from '/imports/api/creature/actions/doAction.js';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import getAncestorContext from '/imports/api/creature/actions/getAncestorContext.js';
|
||||
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory';
|
||||
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties';
|
||||
|
||||
const castSpellWithSlot = new ValidatedMethod({
|
||||
name: 'creatureProperties.castSpellWithSlot',
|
||||
@@ -64,16 +66,24 @@ const castSpellWithSlot = new ValidatedMethod({
|
||||
}
|
||||
let actionContext = getAncestorContext(spell);
|
||||
|
||||
doActionWork({
|
||||
doActionWork({
|
||||
action: spell,
|
||||
actionContext: {slotLevel, ...actionContext},
|
||||
creature,
|
||||
targets: target ? [target] : [],
|
||||
method: this,
|
||||
});
|
||||
// Note this only recomputes the top-level creature, not the nearest one
|
||||
recomputeCreatureByDoc(creature);
|
||||
|
||||
// Note these lines only recompute the top-level creature, not the nearest one
|
||||
// The acting creature might have a new item
|
||||
recomputeInventory(creature._id);
|
||||
// The spell might add properties which need to be activated
|
||||
recomputeInactiveProperties(creature._id);
|
||||
recomputeCreatureByDoc(creature);
|
||||
|
||||
if (target){
|
||||
recomputeInventory(target._id);
|
||||
recomputeInactiveProperties(target._id);
|
||||
recomputeCreatureByDoc(target);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let CreatureFolders = new Mongo.Collection('creatureFolders');
|
||||
|
||||
@@ -7,6 +8,7 @@ let creatureFolderSchema = new SimpleSchema({
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
creatures: {
|
||||
type: Array,
|
||||
|
||||
@@ -5,6 +5,7 @@ import ChildSchema from '/imports/api/parenting/ChildSchema.js';
|
||||
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
|
||||
import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let CreatureProperties = new Mongo.Collection('creatureProperties');
|
||||
|
||||
@@ -16,9 +17,11 @@ let CreaturePropertySchema = new SimpleSchema({
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@@ -27,6 +30,13 @@ let CreaturePropertySchema = new SimpleSchema({
|
||||
icon: {
|
||||
type: storedIconsSchema,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.icon,
|
||||
},
|
||||
// Reference to the library node that this property was copied from
|
||||
libraryNodeId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
optional: true,
|
||||
},
|
||||
// Denormalised flag if this property is inactive on the sheet for any reason
|
||||
// Including being disabled, or a decendent of a disabled property
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
export default function getSlotFillFilter({slot, libraryIds}){
|
||||
let filter = {
|
||||
removed: {$ne: true},
|
||||
$and: []
|
||||
};
|
||||
if (libraryIds){
|
||||
filter['ancestors.id'] = {$in: libraryIds};
|
||||
}
|
||||
if (slot.slotType){
|
||||
filter.$and.push({
|
||||
$or: [{
|
||||
type: slot.slotType
|
||||
},{
|
||||
type: 'slotFiller',
|
||||
slotFillerType: slot.slotType,
|
||||
}]
|
||||
});
|
||||
}
|
||||
let tagsOr = [];
|
||||
let tagsNor = [];
|
||||
if (slot.slotTags && slot.slotTags.length){
|
||||
tagsOr.push({tags: {$all: slot.slotTags}});
|
||||
}
|
||||
if (slot.extraTags && slot.extraTags.length){
|
||||
slot.extraTags.forEach(extra => {
|
||||
if (!extra.tags || !extra.tags.length) return;
|
||||
if (extra.operation === 'OR'){
|
||||
tagsOr.push({tags: {$all: extra.tags}});
|
||||
} else if (extra.operation === 'NOT'){
|
||||
tagsNor.push({tags: {$all: extra.tags}});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (tagsOr.length){
|
||||
filter.$and.push({$or: tagsOr});
|
||||
}
|
||||
if (tagsNor.length){
|
||||
filter.$and.push({$nor: tagsNor});
|
||||
}
|
||||
if (!filter.$and.length){
|
||||
delete filter.$and;
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
@@ -107,6 +107,9 @@ function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
// It must get the first generated ID to prevent flickering
|
||||
nodes = [node, ...nodes];
|
||||
|
||||
// set libraryNodeIds
|
||||
storeLibraryNodeReferences(nodes, nodeId);
|
||||
|
||||
// re-map all the ancestors
|
||||
setLineageOfDocs({
|
||||
docArray: nodes,
|
||||
@@ -135,6 +138,13 @@ function insertPropertyFromNode(nodeId, ancestors, order){
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
function storeLibraryNodeReferences(nodes){
|
||||
nodes.forEach(node => {
|
||||
node.libraryNodeId = node._id;
|
||||
});
|
||||
}
|
||||
|
||||
// Covert node references into actual nodes
|
||||
// TODO: check permissions for each library a reference node references
|
||||
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import { get } from 'lodash';
|
||||
|
||||
const pushToProperty = new ValidatedMethod({
|
||||
name: 'creatureProperties.push',
|
||||
@@ -19,9 +20,26 @@ const pushToProperty = new ValidatedMethod({
|
||||
let rootCreature = getRootCreatureAncestor(property);
|
||||
assertEditPermission(rootCreature, this.userId);
|
||||
|
||||
let joinedPath = path.join('.');
|
||||
|
||||
// Respect maxCount
|
||||
let schema = CreatureProperties.simpleSchema(property);
|
||||
let maxCount = schema.get(joinedPath, 'maxCount');
|
||||
|
||||
if (Number.isFinite(maxCount)){
|
||||
let array = get(property, path);
|
||||
let currentCount = array ? array.length : 0;
|
||||
if (currentCount >= maxCount){
|
||||
throw new Meteor.Error(
|
||||
'Array is full',
|
||||
`Cannot have more than ${maxCount} values`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Do work
|
||||
CreatureProperties.update(_id, {
|
||||
$push: {[path.join('.')]: value},
|
||||
$push: {[joinedPath]: value},
|
||||
}, {
|
||||
selector: {type: property.type},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
||||
import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema.js'
|
||||
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
|
||||
import SharingSchema from '/imports/api/sharing/SharingSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
//set up the collection for creatures
|
||||
let Creatures = new Mongo.Collection('creatures');
|
||||
@@ -47,7 +48,7 @@ let CreatureSettingsSchema = new SimpleSchema({
|
||||
discordWebhook: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: 200,
|
||||
max: STORAGE_LIMITS.url,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,22 +58,27 @@ let CreatureSchema = new SimpleSchema({
|
||||
type: String,
|
||||
defaultValue: '',
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
alignment: {
|
||||
type: String,
|
||||
optional: true
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
gender: {
|
||||
type: String,
|
||||
optional: true
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
picture: {
|
||||
type: String,
|
||||
optional: true
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.url,
|
||||
},
|
||||
avatarPicture: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.url,
|
||||
},
|
||||
// Mechanics
|
||||
deathSave: {
|
||||
|
||||
5
app/imports/api/creature/creatures/getCreatureUrlName.js
Normal file
5
app/imports/api/creature/creatures/getCreatureUrlName.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import getSlug from 'speakingurl';
|
||||
|
||||
export default function getCreatureUrlName({name}){
|
||||
return getSlug(name, {maintainCase: true}) || '-';
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import '/imports/api/creature/creatures/methods/insertCreature.js';
|
||||
import '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||
import '/imports/api/creature/creatures/methods/restCreature.js';
|
||||
import '/imports/api/creature/creatures/methods/transferCreatureOwnership.js';
|
||||
import '/imports/api/creature/creatures/methods/updateCreature.js';
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { assertOwnership } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
const transferCreatureOwnership = new ValidatedMethod({
|
||||
|
||||
name: 'creatures.methods.transferOwnership',
|
||||
|
||||
validate: new SimpleSchema({
|
||||
creatureId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
userId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
|
||||
run({creatureId, userId}) {
|
||||
assertOwnership(creatureId, this.userId);
|
||||
|
||||
let tier = getUserTier(userId);
|
||||
let currentCharacterCount = Creatures.find({
|
||||
owner: userId,
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
}).count();
|
||||
|
||||
if (
|
||||
tier.characterSlots !== -1 &&
|
||||
currentCharacterCount >= tier.characterSlots
|
||||
){
|
||||
throw new Meteor.Error('Creatures.methods.transferOwnership.denied',
|
||||
'The new owner is already at their character limit')
|
||||
}
|
||||
|
||||
Creatures.update(creatureId, {
|
||||
$set: {owner: userId},
|
||||
});
|
||||
|
||||
return creatureId;
|
||||
},
|
||||
});
|
||||
|
||||
export default transferCreatureOwnership;
|
||||
@@ -1,10 +1,10 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let Experiences = new Mongo.Collection('experiences');
|
||||
|
||||
@@ -12,6 +12,7 @@ let ExperienceSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The amount of XP this experience gives
|
||||
xp: {
|
||||
@@ -90,11 +91,6 @@ const insertExperience = new ValidatedMethod({
|
||||
throw new Meteor.Error('Experiences.methods.insert.denied',
|
||||
'You need to be logged in to insert an experience');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits){
|
||||
throw new Meteor.Error('Experiences.methods.insert.denied',
|
||||
`The ${tier.name} tier does not allow you to grant experience`);
|
||||
}
|
||||
let insertedIds = [];
|
||||
creatureIds.forEach(creatureId => {
|
||||
let id = insertExperienceForCreature({experience, creatureId, userId});
|
||||
@@ -123,11 +119,6 @@ const removeExperience = new ValidatedMethod({
|
||||
throw new Meteor.Error('Experiences.methods.remove.denied',
|
||||
'You need to be logged in to remove an experience');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits){
|
||||
throw new Meteor.Error('Experiences.methods.remove.denied',
|
||||
`The ${tier.name} tier does not allow you to remove an experience`);
|
||||
}
|
||||
let experience = Experiences.findOne(experienceId);
|
||||
if (!experience) return;
|
||||
let creatureId = experience.creatureId
|
||||
@@ -168,11 +159,6 @@ const recomputeExperiences = new ValidatedMethod({
|
||||
throw new Meteor.Error('Experiences.methods.recompute.denied',
|
||||
'You need to be logged in to recompute a creature\'s experiences');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits){
|
||||
throw new Meteor.Error('Experiences.methods.recompute.denied',
|
||||
`The ${tier.name} tier does not allow you to recompute a creature's experiences`);
|
||||
}
|
||||
assertEditPermission(creatureId, userId);
|
||||
|
||||
let xp = 0;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let ExperienceSchema = new SimpleSchema({
|
||||
title: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// Potentially long description of the event
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// The real-world date that it occured
|
||||
date: {
|
||||
@@ -24,14 +27,17 @@ let ExperienceSchema = new SimpleSchema({
|
||||
worldDate: {
|
||||
type: String,
|
||||
optional: true,
|
||||
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: {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
prettifyParseError
|
||||
} from '/imports/parser/parser.js';
|
||||
const PER_CREATURE_LOG_LIMIT = 100;
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
if (Meteor.isServer){
|
||||
var sendWebhookAsCreature = require('/imports/server/discord/sendWebhook.js').sendWebhookAsCreature;
|
||||
@@ -21,7 +22,7 @@ let CreatureLogSchema = new SimpleSchema({
|
||||
content: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 25,
|
||||
maxCount: STORAGE_LIMITS.logContentCount,
|
||||
},
|
||||
'content.$': {
|
||||
type: LogContentSchema,
|
||||
@@ -45,6 +46,7 @@ let CreatureLogSchema = new SimpleSchema({
|
||||
creatureName: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import RollDetailsSchema from '/imports/api/properties/subSchemas/RollDetailsSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let LogContentSchema = new SimpleSchema({
|
||||
// The name of the field, included in discord webhook message
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The details of the field, included in discord webhook message
|
||||
// Markdown support
|
||||
value: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
context: {
|
||||
type: Object,
|
||||
@@ -21,6 +24,7 @@ let LogContentSchema = new SimpleSchema({
|
||||
'context.errors':{
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'context.errors.$': {
|
||||
type: ErrorSchema,
|
||||
@@ -28,6 +32,7 @@ let LogContentSchema = new SimpleSchema({
|
||||
'context.rolls': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.rollCount,
|
||||
},
|
||||
'context.rolls.$': {
|
||||
type: RollDetailsSchema,
|
||||
|
||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let Icons = new Mongo.Collection('icons');
|
||||
|
||||
@@ -9,22 +10,27 @@ let iconsSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
unique: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
index: 1,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
index: 1,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.icon,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js
|
||||
import { assertEditPermission, assertOwnership } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js'
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/**
|
||||
* Libraries are trees of library nodes where each node represents a character
|
||||
@@ -21,11 +22,8 @@ let Libraries = new Mongo.Collection('libraries');
|
||||
let LibrarySchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
isDefault: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
LibrarySchema.extend(SharingSchema);
|
||||
@@ -39,7 +37,7 @@ const insertLibrary = new ValidatedMethod({
|
||||
mixins: [
|
||||
simpleSchemaMixin,
|
||||
],
|
||||
schema: LibrarySchema.omit('owner', 'isDefault'),
|
||||
schema: LibrarySchema.omit('owner'),
|
||||
run(library) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
@@ -78,30 +76,6 @@ const updateLibraryName = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
const setLibraryDefault = new ValidatedMethod({
|
||||
name: 'libraries.makeLibraryDefault',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
isDefault: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({_id, isDefault}) {
|
||||
if (!Meteor.users.isAdmin()){
|
||||
throw new Meteor.Error('Permission denied', 'User must be admin to set libraries as default');
|
||||
}
|
||||
return Libraries.update(_id, {$set: {isDefault}});
|
||||
},
|
||||
});
|
||||
|
||||
const removeLibrary = new ValidatedMethod({
|
||||
name: 'libraries.remove',
|
||||
validate: new SimpleSchema({
|
||||
@@ -128,4 +102,4 @@ export function removeLibaryWork(libraryId){
|
||||
LibraryNodes.remove({'ancestors.id': libraryId});
|
||||
}
|
||||
|
||||
export { LibrarySchema, insertLibrary, setLibraryDefault, updateLibraryName, removeLibrary };
|
||||
export { LibrarySchema, insertLibrary, updateLibraryName, removeLibrary };
|
||||
|
||||
@@ -13,6 +13,7 @@ import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
|
||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||
import '/imports/api/library/methods/index.js';
|
||||
import { updateReferenceNodeWork } from '/imports/api/library/methods/updateReferenceNode.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let LibraryNodes = new Mongo.Collection('libraryNodes');
|
||||
|
||||
@@ -24,13 +25,16 @@ let LibraryNodeSchema = new SimpleSchema({
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
icon: {
|
||||
type: storedIconsSchema,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.icon,
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
Blacklist = new Mongo.Collection("blacklist");
|
||||
|
||||
Schemas.Blacklist = new SimpleSchema({
|
||||
userId: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
Blacklist.attachSchema(Schemas.Blacklist);
|
||||
@@ -1,29 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
ChangeLogs = new Mongo.Collection("changeLogs");
|
||||
|
||||
Schemas.ChangeLog = new SimpleSchema({
|
||||
version: {
|
||||
type: String,
|
||||
},
|
||||
changes: {
|
||||
type: [String],
|
||||
},
|
||||
});
|
||||
|
||||
ChangeLogs.attachSchema(Schemas.ChangeLog);
|
||||
|
||||
ChangeLogs.allow({
|
||||
insert: function(userId, doc) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
update: function(userId, doc, fields, modifier) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
remove: function(userId, doc) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
});
|
||||
@@ -1,81 +0,0 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
Reports = new Mongo.Collection("reports");
|
||||
|
||||
Schemas.Report = new SimpleSchema({
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: ["General Feedback", "Bug", "Suggested Change", "Feature Request"],
|
||||
defaultValue: "General Feedback",
|
||||
},
|
||||
//the immediate impact of doing this action (eg. -1 rages)
|
||||
severity: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 5,
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
metaData: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
});
|
||||
|
||||
Reports.attachSchema(Schemas.Report);
|
||||
|
||||
Meteor.methods({
|
||||
insertReport: function(report) {
|
||||
check(report, {
|
||||
title: String,
|
||||
description: String,
|
||||
type: String,
|
||||
severity: Number,
|
||||
metaData: Object,
|
||||
});
|
||||
report.owner = this.userId;
|
||||
var id = Reports.insert(report);
|
||||
var user = Meteor.users.findOne(this.userId);
|
||||
var sender = user &&
|
||||
user.emails &&
|
||||
user.emails[0] &&
|
||||
user.emails[0].address ||
|
||||
user.services &&
|
||||
user.services.google &&
|
||||
user.services.google.email ||
|
||||
"reports@dicecloud.com";
|
||||
var bodyText = "Report ID: " + id +
|
||||
"\nSeverity: " + report.severity +
|
||||
"\nType: " + report.type +
|
||||
"\n\n" + report.description;
|
||||
Email.send({
|
||||
from: sender,
|
||||
to: "stefan.zermatten@gmail.com",
|
||||
subject: "DiceCloud feedback - " + report.title,
|
||||
text: bodyText,
|
||||
});
|
||||
},
|
||||
deleteReport: function(id) {
|
||||
var user = Meteor.users.findOne(this.userId);
|
||||
if (!_.contains(user.roles, "admin")){
|
||||
throw new Meteor.Error(
|
||||
"not admin",
|
||||
"The user must be an administrator to delete feedback"
|
||||
);
|
||||
}
|
||||
Reports.remove(id);
|
||||
},
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const RefSchema = new SimpleSchema({
|
||||
id: {
|
||||
@@ -12,7 +13,8 @@ const RefSchema = new SimpleSchema({
|
||||
index: 1
|
||||
},
|
||||
collection: {
|
||||
type: String
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,7 +29,7 @@ let ChildSchema = new SimpleSchema({
|
||||
ancestors: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
max: 100,
|
||||
maxCount: STORAGE_LIMITS.ancestorCount,
|
||||
},
|
||||
'ancestors.$': {
|
||||
type: RefSchema,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js'
|
||||
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
* Actions are things a character can do
|
||||
* Any rolls that are children of actions will be rolled when taking the action
|
||||
@@ -12,14 +14,17 @@ let ActionSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
summary: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// What time-resource is used to take the action in combat
|
||||
// long actions take longer than 1 round to cast
|
||||
@@ -38,13 +43,6 @@ let ActionSchema = new SimpleSchema({
|
||||
'multipleTargets',
|
||||
],
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
},
|
||||
// Duplicate the ResourceSchema here so we can extend it elegantly.
|
||||
resources: {
|
||||
type: Object,
|
||||
@@ -53,6 +51,7 @@ let ActionSchema = new SimpleSchema({
|
||||
'resources.itemsConsumed': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.resourcesCount,
|
||||
},
|
||||
'resources.itemsConsumed.$': {
|
||||
type: Object,
|
||||
@@ -67,6 +66,7 @@ let ActionSchema = new SimpleSchema({
|
||||
'resources.itemsConsumed.$.tag': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
'resources.itemsConsumed.$.quantity': {
|
||||
type: Number,
|
||||
@@ -75,10 +75,12 @@ let ActionSchema = new SimpleSchema({
|
||||
'resources.itemsConsumed.$.itemId': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
'resources.attributesConsumed': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.resourcesCount,
|
||||
},
|
||||
'resources.attributesConsumed.$': {
|
||||
type: Object,
|
||||
@@ -93,6 +95,7 @@ let ActionSchema = new SimpleSchema({
|
||||
'resources.attributesConsumed.$.variableName': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
'resources.attributesConsumed.$.quantity': {
|
||||
type: Number,
|
||||
@@ -102,6 +105,7 @@ let ActionSchema = new SimpleSchema({
|
||||
uses: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Integer of how many times it has already been used
|
||||
usesUsed: {
|
||||
@@ -120,14 +124,14 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
||||
summaryCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'summaryCalculations.$': InlineComputationSchema,
|
||||
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
|
||||
@@ -138,6 +142,7 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
||||
usesErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'usesErrors.$':{
|
||||
type: ErrorSchema,
|
||||
@@ -158,15 +163,18 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
||||
},
|
||||
'resources.itemsConsumed.$.itemName': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
optional: true,
|
||||
},
|
||||
'resources.itemsConsumed.$.itemIcon': {
|
||||
type: storedIconsSchema,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.icon,
|
||||
},
|
||||
'resources.itemsConsumed.$.itemColor': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.color,
|
||||
},
|
||||
'resources.attributesConsumed': Array,
|
||||
'resources.attributesConsumed.$': Object,
|
||||
@@ -182,6 +190,7 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
||||
'resources.attributesConsumed.$.statName': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// True if the uses left is zero, or any item or attribute consumed is
|
||||
// insufficient
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const AdjustmentSchema = new SimpleSchema({
|
||||
// The roll that determines how much to change the attribute
|
||||
@@ -8,6 +9,7 @@ const AdjustmentSchema = new SimpleSchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: '1',
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Who this adjustment applies to
|
||||
target: {
|
||||
@@ -23,6 +25,7 @@ const AdjustmentSchema = new SimpleSchema({
|
||||
stat: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
operation: {
|
||||
type: String,
|
||||
@@ -39,6 +42,7 @@ const ComputedOnlyAdjustmentSchema = new SimpleSchema({
|
||||
amountErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'amountErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ActionSchema, ComputedOnlyActionSchema } from '/imports/api/properties/Actions.js';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
// Attacks are special instances of actions
|
||||
let AttackSchema = new SimpleSchema()
|
||||
@@ -11,18 +12,22 @@ let AttackSchema = new SimpleSchema()
|
||||
type: String,
|
||||
defaultValue: 'strength.modifier + proficiencyBonus',
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Set better defaults for the action
|
||||
actionType: {
|
||||
type: String,
|
||||
defaultValue: 'attack',
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: ['attack'],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -36,6 +41,7 @@ const ComputedOnlyAttackSchema = new SimpleSchema()
|
||||
rollBonusErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'rollBonusErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
* Attributes are numbered stats of a character
|
||||
@@ -10,6 +11,7 @@ let AttributeSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: 'New Attribute',
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
@@ -17,6 +19,7 @@ let AttributeSchema = new SimpleSchema({
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
defaultValue: 'newAttribute',
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// How it is displayed and computed is determined by type
|
||||
attributeType: {
|
||||
@@ -45,16 +48,19 @@ let AttributeSchema = new SimpleSchema({
|
||||
spellSlotLevelCalculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// The starting value, before effects
|
||||
baseValueCalculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Description of what the attribute is used for
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// The damage done to the attribute, always positive
|
||||
damage: {
|
||||
@@ -79,7 +85,7 @@ let ComputedOnlyAttributeSchema = new SimpleSchema({
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
// The result of baseValueCalculation
|
||||
@@ -93,6 +99,7 @@ let ComputedOnlyAttributeSchema = new SimpleSchema({
|
||||
},
|
||||
'baseValueErrors.$': {
|
||||
type: ErrorSchema,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
// The result of spellSlotLevelCalculation
|
||||
spellSlotLevelValue: {
|
||||
@@ -102,6 +109,7 @@ let ComputedOnlyAttributeSchema = new SimpleSchema({
|
||||
spellSlotLevelErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'spellSlotLevelErrors.$': {
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let BuffSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
duration: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
applied: {
|
||||
type: Boolean,
|
||||
@@ -34,7 +38,7 @@ let ComputedOnlyBuffSchema = new SimpleSchema({
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
durationSpent: {
|
||||
@@ -48,6 +52,7 @@ let ComputedOnlyBuffSchema = new SimpleSchema({
|
||||
},
|
||||
'appliedBy.name': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
'appliedBy.id': {
|
||||
type: String,
|
||||
@@ -55,6 +60,7 @@ let ComputedOnlyBuffSchema = new SimpleSchema({
|
||||
},
|
||||
'appliedBy.collection': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let ClassLevelSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// Only used by slot filling dialog, not computed
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// The name of this class level's variable
|
||||
variableName: {
|
||||
type: String,
|
||||
min: 2,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
@@ -32,6 +36,7 @@ let ClassLevelSchema = new SimpleSchema({
|
||||
slotFillerCondition: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
} from '/imports/parser/parser.js';
|
||||
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
|
||||
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
* Constants are primitive values that can be used elsewhere in computations
|
||||
*/
|
||||
@@ -15,6 +17,7 @@ let ConstantSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
@@ -22,15 +25,18 @@ let ConstantSchema = new SimpleSchema({
|
||||
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
|
||||
// of constant nodes to be valid
|
||||
calculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
errors: {
|
||||
type: Array,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
autoValue(){
|
||||
let calc = this.field('calculation');
|
||||
if (!calc.isSet && this.isModifier) {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let ContainerSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false
|
||||
trim: false,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
carried: {
|
||||
type: Boolean,
|
||||
@@ -29,7 +31,8 @@ let ContainerSchema = new SimpleSchema({
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false
|
||||
trim: false,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,7 +40,7 @@ const ComputedOnlyContainerSchema = new SimpleSchema({
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
// Weight of all the contents, zero if `contentsWeightless` is true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
* DamageMultipliers are multipliers that affect how much damage is taken from
|
||||
@@ -9,10 +10,12 @@ let DamageMultiplierSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
damageTypes: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.damageTypeCount,
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
'damageTypes.$': {
|
||||
@@ -29,17 +32,21 @@ let DamageMultiplierSchema = new SimpleSchema({
|
||||
excludeTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'excludeTags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
// Tags which must be present to be affected by this multiplier (AND)
|
||||
includeTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'includeTags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const DamageSchema = new SimpleSchema({
|
||||
// The roll that determines how much to damage the attribute
|
||||
@@ -9,6 +10,7 @@ const DamageSchema = new SimpleSchema({
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: '1d8 + strength.modifier',
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Who this damage applies to
|
||||
target: {
|
||||
@@ -35,6 +37,7 @@ const ComputedOnlyDamageSchema = new SimpleSchema({
|
||||
amountErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'amountErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
* Effects are reason-value attached to skills and abilities
|
||||
* that modify their final value or presentation in some way
|
||||
@@ -8,6 +10,7 @@ let EffectSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
operation: {
|
||||
type: String,
|
||||
@@ -30,14 +33,17 @@ let EffectSchema = new SimpleSchema({
|
||||
calculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
//which stats the effect is applied to
|
||||
stats: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.statsToTarget,
|
||||
},
|
||||
'stats.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,6 +57,7 @@ const ComputedOnlyEffectSchema = new SimpleSchema({
|
||||
errors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'errors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let FeatureSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
summary: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,14 +24,14 @@ let ComputedOnlyFeatureSchema = new SimpleSchema({
|
||||
summaryCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'summaryCalculations.$': InlineComputationSchema,
|
||||
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
// Folders organize a character sheet into a tree, particularly to group things
|
||||
// like 'race' and 'background'
|
||||
let FolderSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const ItemSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// Plural name of the item, if there is more than one
|
||||
plural: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// Number currently held
|
||||
quantity: {
|
||||
@@ -58,7 +62,7 @@ let ComputedOnlyItemSchema = new SimpleSchema({
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
});
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let NoteSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
summary: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -21,14 +25,14 @@ let ComputedOnlyNoteSchema = new SimpleSchema({
|
||||
summaryCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'summaryCalculations.$': InlineComputationSchema,
|
||||
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let ProficiencySchema = new SimpleSchema({
|
||||
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: [],
|
||||
maxCount: STORAGE_LIMITS.statsToTarget,
|
||||
},
|
||||
'stats.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// A number representing how proficient the character is
|
||||
// where 0.49 is half rounded down and 0.5 is half rounded up
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let ReferenceSchema = new SimpleSchema({
|
||||
ref: {
|
||||
@@ -13,6 +14,7 @@ let ReferenceSchema = new SimpleSchema({
|
||||
'ref.collection': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
},
|
||||
// Denormalised store of referenced property's details
|
||||
cache: {
|
||||
@@ -22,6 +24,7 @@ let ReferenceSchema = new SimpleSchema({
|
||||
'cache.error': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.errorMessage,
|
||||
},
|
||||
'cache.node': {
|
||||
type: Object,
|
||||
@@ -30,9 +33,11 @@ let ReferenceSchema = new SimpleSchema({
|
||||
'cache.node.name': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
'cache.node.type': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
'cache.node.level': {
|
||||
type: Number,
|
||||
@@ -49,6 +54,7 @@ let ReferenceSchema = new SimpleSchema({
|
||||
'cache.library.name': {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/**
|
||||
* Rolls are children to actions or other rolls, they are triggered with 0 or
|
||||
@@ -24,6 +25,7 @@ let RollSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: 'New Roll',
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
variableName: {
|
||||
@@ -31,20 +33,13 @@ let RollSchema = new SimpleSchema({
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
defaultValue: 'newRoll',
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// The roll, can be simplified, but only computed in context
|
||||
roll: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
// Effects can apply to this tag specifically
|
||||
// Ranged spell attack, Ranged weapon attack, etc.
|
||||
tags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
'tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -56,6 +51,7 @@ let ComputedOnlyRollSchema = new SimpleSchema({
|
||||
rollErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'rollErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
// These are the rolls made when saves are called for
|
||||
// For the saving throw bonus or proficiency, see ./Skills.js
|
||||
@@ -7,11 +8,13 @@ let SavingThrowSchema = new SimpleSchema ({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The computed DC
|
||||
dc: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Who this saving throw applies to
|
||||
target: {
|
||||
@@ -27,6 +30,7 @@ let SavingThrowSchema = new SimpleSchema ({
|
||||
stat: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -38,6 +42,7 @@ const ComputedOnlySavingThrowSchema = new SimpleSchema({
|
||||
dcErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'dcErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
/*
|
||||
* Skills are anything that results in a modifier to be added to a D20
|
||||
@@ -10,6 +11,7 @@ let SkillSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// The technical, lowercase, single-word name used in formulae
|
||||
// Ignored for skilltype = save
|
||||
@@ -17,11 +19,13 @@ let SkillSchema = new SimpleSchema({
|
||||
type: String,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// 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
|
||||
skillType: {
|
||||
@@ -42,6 +46,7 @@ let SkillSchema = new SimpleSchema({
|
||||
baseValueCalculation: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// The base proficiency of this skill
|
||||
baseProficiency: {
|
||||
@@ -52,6 +57,7 @@ let SkillSchema = new SimpleSchema({
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -69,6 +75,7 @@ let ComputedOnlySkillSchema = new SimpleSchema({
|
||||
baseValueErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'baseValueErrors.$': {
|
||||
type: ErrorSchema,
|
||||
@@ -107,6 +114,7 @@ let ComputedOnlySkillSchema = new SimpleSchema({
|
||||
rollBonuses: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.rollBonusCount,
|
||||
},
|
||||
'rollBonuses.$': {
|
||||
type: String,
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
// SlotFiller fillers specifically fill a slot with a bit more control than
|
||||
// other properties
|
||||
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
let SlotFillerSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
picture: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.url,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// Overrides the type when searching for properties
|
||||
slotFillerType: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// Fill more than one quantity in a slot, like feats and ability score
|
||||
// improvements, filtered out of UI if there isn't space in quantityExpected
|
||||
@@ -32,6 +36,7 @@ let SlotFillerSchema = new SimpleSchema({
|
||||
slotFillerCondition: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,30 +1,66 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let SlotSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
slotType: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
slotTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'slotTags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
extraTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.extraTagsCount,
|
||||
},
|
||||
'extraTags.$': {
|
||||
type: Object,
|
||||
},
|
||||
'extraTags.$._id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
'extraTags.$.operation': {
|
||||
type: String,
|
||||
allowedValues: ['OR', 'NOT'],
|
||||
defaultValue: 'OR',
|
||||
},
|
||||
'extraTags.$.tags': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'extraTags.$.tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
quantityExpected: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: '1',
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
ignored: {
|
||||
type: Boolean,
|
||||
@@ -33,11 +69,24 @@ let SlotSchema = new SimpleSchema({
|
||||
slotCondition: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
hideWhenFull: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
}
|
||||
defaultValue: true,
|
||||
},
|
||||
unique: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
// Can't choose the same slot filler twice in this slot
|
||||
'uniqueInSlot',
|
||||
// Can't choose the same slot filler twice accross the whole creature
|
||||
'uniqueInCreature'
|
||||
],
|
||||
optional: true,
|
||||
defaultValue: 'uniqueInSlot',
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlySlotSchema = new SimpleSchema({
|
||||
@@ -49,6 +98,7 @@ const ComputedOnlySlotSchema = new SimpleSchema({
|
||||
slotConditionErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'slotConditionErrors.$':{
|
||||
type: ErrorSchema,
|
||||
@@ -62,6 +112,7 @@ const ComputedOnlySlotSchema = new SimpleSchema({
|
||||
quantityExpectedErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'quantityExpectedErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let SpellListSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
// Calculation of how many spells in this list can be prepared
|
||||
maxPrepared: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Calculation of The attack roll bonus used by spell attacks in this list
|
||||
attackRollBonus: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
// Calculation of the save dc used by spells in this list
|
||||
dc: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,7 +38,7 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
descriptionCalculations: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: 32,
|
||||
maxCount: STORAGE_LIMITS.inlineCalculationCount,
|
||||
},
|
||||
'descriptionCalculations.$': InlineComputationSchema,
|
||||
|
||||
@@ -44,6 +50,7 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
maxPreparedErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'maxPreparedErrors.$':{
|
||||
type: ErrorSchema,
|
||||
@@ -57,6 +64,7 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
attackRollBonusErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'attackRollBonusErrors.$':{
|
||||
type: ErrorSchema,
|
||||
@@ -70,6 +78,7 @@ const ComputedOnlySpellListSchema = new SimpleSchema({
|
||||
dcErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'dcErrors.$':{
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ActionSchema, ComputedOnlyActionSchema } from '/imports/api/properties/Actions.js';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const magicSchools = [
|
||||
'abjuration',
|
||||
@@ -18,6 +19,7 @@ let SpellSchema = new SimpleSchema({})
|
||||
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
|
||||
@@ -42,15 +44,18 @@ let SpellSchema = new SimpleSchema({})
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'action',
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
range: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
duration: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'Instantaneous',
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
verbal: {
|
||||
type: Boolean,
|
||||
@@ -67,6 +72,7 @@ let SpellSchema = new SimpleSchema({})
|
||||
material: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
ritual: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const ToggleSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@@ -19,6 +21,7 @@ const ToggleSchema = new SimpleSchema({
|
||||
condition: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,6 +35,7 @@ const ComputedOnlyToggleSchema = new SimpleSchema({
|
||||
errors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'errors.$': {
|
||||
type: ErrorSchema,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const ErrorSchema = new SimpleSchema({
|
||||
message: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.errorMessage,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
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 {}
|
||||
calculation: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
result: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
},
|
||||
errors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.errorCount,
|
||||
},
|
||||
'errors.$': ErrorSchema,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const RollDetailsSchema = new SimpleSchema({
|
||||
number: {
|
||||
@@ -10,6 +11,7 @@ const RollDetailsSchema = new SimpleSchema({
|
||||
values: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.diceRollValuesCount,
|
||||
},
|
||||
'values.$': {
|
||||
type: Number,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import '/imports/api/sharing/sharing.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let SharingSchema = new SimpleSchema({
|
||||
owner: {
|
||||
@@ -11,9 +12,9 @@ let SharingSchema = new SimpleSchema({
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
max: 50,
|
||||
maxCount: STORAGE_LIMITS.readersCount,
|
||||
},
|
||||
"readers.$": {
|
||||
'readers.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
@@ -21,9 +22,9 @@ let SharingSchema = new SimpleSchema({
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
max: 20,
|
||||
maxCount: STORAGE_LIMITS.writersCount,
|
||||
},
|
||||
"writers.$": {
|
||||
'writers.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import getCollectionByName from '/imports/api/parenting/getCollectionByName.js';
|
||||
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||
|
||||
const setPublic = new ValidatedMethod({
|
||||
name: 'sharing.setPublic',
|
||||
@@ -47,7 +48,7 @@ const updateUserSharePermissions = new ValidatedMethod({
|
||||
run({docRef, userId, role}){
|
||||
let doc = fetchDocByRef(docRef);
|
||||
if (role === 'none'){
|
||||
// only asser ownership if you aren't removing yourself
|
||||
// only assert ownership if you aren't removing yourself
|
||||
if (this.userId !== userId){
|
||||
assertOwnership(doc, this.userId);
|
||||
}
|
||||
@@ -74,4 +75,58 @@ const updateUserSharePermissions = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
export { setPublic, updateUserSharePermissions };
|
||||
const transferOwnership = new ValidatedMethod({
|
||||
name: 'sharing.transferOwnership',
|
||||
validate: new SimpleSchema({
|
||||
docRef: RefSchema,
|
||||
userId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({docRef, userId}){
|
||||
let doc = fetchDocByRef(docRef);
|
||||
assertOwnership(doc, this.userId);
|
||||
|
||||
let collection = getCollectionByName(docRef.collection);
|
||||
|
||||
let tier = getUserTier(userId);
|
||||
if (docRef.collection === 'creatures'){
|
||||
let currentCharacterCount = collection.find({
|
||||
owner: userId,
|
||||
}, {
|
||||
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')
|
||||
}
|
||||
} 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');
|
||||
}
|
||||
}
|
||||
|
||||
// First remove current permissions for the user
|
||||
collection.update(docRef.id, {
|
||||
$pullAll: { writers: userId, readers: userId },
|
||||
});
|
||||
// Then make the user the owner and the current owner a writer
|
||||
return collection.update(docRef.id, {
|
||||
$set: {owner: userId},
|
||||
$addToSet: { writers: this.userId },
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export { setPublic, updateUserSharePermissions, transferOwnership };
|
||||
|
||||
32
app/imports/constants/STORAGE_LIMITS.js
Normal file
32
app/imports/constants/STORAGE_LIMITS.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const STORAGE_LIMITS = Object.freeze({
|
||||
// String lengths
|
||||
calculation: 256,
|
||||
collectionName: 64,
|
||||
color: 10000,
|
||||
description: 49473, //the length of the Bee Movie script
|
||||
errorMessage: 256,
|
||||
icon: 10000,
|
||||
name: 128,
|
||||
summary: 10000,
|
||||
tagLength: 128,
|
||||
url: 256,
|
||||
variableName: 64,
|
||||
|
||||
//Array counts
|
||||
ancestorCount: 100,
|
||||
damageTypeCount: 32,
|
||||
diceRollValuesCount: 100,
|
||||
errorCount: 32,
|
||||
extraTagsCount: 5,
|
||||
inlineCalculationCount: 32,
|
||||
logContentCount: 32,
|
||||
readersCount: 50,
|
||||
resourcesCount: 32,
|
||||
rollCount: 64,
|
||||
rollBonusCount: 32,
|
||||
statsToTarget: 32,
|
||||
tagCount: 64,
|
||||
writersCount: 20,
|
||||
});
|
||||
|
||||
export default STORAGE_LIMITS;
|
||||
@@ -2,6 +2,7 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js';
|
||||
import RollArrayNode from '/imports/parser/parseTree/RollArrayNode.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
import roll from '/imports/parser/roll.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
export default class RollNode extends ParseNode {
|
||||
constructor({left, right}) {
|
||||
@@ -42,9 +43,9 @@ export default class RollNode extends ParseNode {
|
||||
if (context.doubleRolls){
|
||||
number *= 2;
|
||||
}
|
||||
if (number > 100) return new ErrorNode({
|
||||
if (number > STORAGE_LIMITS.diceRollValuesCount) return new ErrorNode({
|
||||
node: this,
|
||||
error: 'Can\'t roll more than 100 dice at once',
|
||||
error: `Can't roll more than ${STORAGE_LIMITS.diceRollValuesCount} dice at once`,
|
||||
context,
|
||||
});
|
||||
let diceSize = right.value;
|
||||
|
||||
@@ -2,8 +2,11 @@ import { check } from 'meteor/check';
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js'
|
||||
|
||||
Meteor.publish('slotFillers', function(slotId, searchTerm){
|
||||
if (searchTerm) check(searchTerm, String);
|
||||
|
||||
Meteor.publish('slotFillers', function(slotId){
|
||||
let self = this;
|
||||
this.autorun(function (){
|
||||
let userId = this.userId;
|
||||
@@ -21,7 +24,7 @@ Meteor.publish('slotFillers', function(slotId){
|
||||
fields: {subscribedLibraries: 1}
|
||||
});
|
||||
const subs = user && user.subscribedLibraries || [];
|
||||
let libraryIds = Libraries.find({
|
||||
let libraries = Libraries.find({
|
||||
$or: [
|
||||
{owner: this.userId},
|
||||
{writers: this.userId},
|
||||
@@ -29,45 +32,29 @@ Meteor.publish('slotFillers', function(slotId){
|
||||
{_id: {$in: subs}},
|
||||
]
|
||||
}, {
|
||||
fields: {_id: 1},
|
||||
}).map(lib => lib._id);
|
||||
fields: {_id: 1, name: 1},
|
||||
});
|
||||
let libraryIds = libraries.map(lib => lib._id);
|
||||
|
||||
// Build a filter for nodes in those libraries that match the slot
|
||||
let filter = {
|
||||
'ancestors.id': {$in: libraryIds},
|
||||
removed: {$ne: true},
|
||||
};
|
||||
if (slot.slotTags && slot.slotTags.length){
|
||||
filter.tags = {$all: slot.slotTags};
|
||||
}
|
||||
if (slot.slotType){
|
||||
filter.$or = [{
|
||||
type: slot.slotType
|
||||
},{
|
||||
type: 'slotFiller',
|
||||
slotFillerType: slot.slotType,
|
||||
}];
|
||||
}
|
||||
let filter = getSlotFillFilter({slot, libraryIds});
|
||||
|
||||
this.autorun(function(){
|
||||
// Get the limit of the documents the user can fetch
|
||||
var limit = self.data('limit') || 20;
|
||||
var limit = self.data('limit') || 50;
|
||||
check(limit, Number);
|
||||
|
||||
// Get the search term
|
||||
let searchTerm = self.data('searchTerm') || '';
|
||||
check(searchTerm, String);
|
||||
|
||||
let options = undefined;
|
||||
if (searchTerm){
|
||||
filter.$text = {$search: searchTerm};
|
||||
options = {
|
||||
// relevant documents have a higher score.
|
||||
fields: {
|
||||
score: { $meta: 'textScore' }
|
||||
_score: { $meta: 'textScore' }
|
||||
},
|
||||
sort: {
|
||||
// `score` property specified in the projection fields above.
|
||||
score: { $meta: 'textScore' },
|
||||
_score: { $meta: 'textScore' },
|
||||
name: 1,
|
||||
order: 1,
|
||||
}
|
||||
@@ -85,7 +72,8 @@ Meteor.publish('slotFillers', function(slotId){
|
||||
self.setData('countAll', LibraryNodes.find(filter).count());
|
||||
});
|
||||
self.autorun(function () {
|
||||
return LibraryNodes.find(filter, options);
|
||||
Meteor._sleepForMs(1000);
|
||||
return [LibraryNodes.find(filter, options), libraries];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
opacity: 0.4;
|
||||
}
|
||||
.found {
|
||||
background: rgba(200, 0, 0, 0.1);
|
||||
background: rgba(200, 0, 0, 0.1) !important;
|
||||
}
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<template>
|
||||
if<template>
|
||||
<div class="character-sheet fill-height">
|
||||
<v-fade-transition mode="out-in">
|
||||
<div
|
||||
@@ -35,6 +35,10 @@
|
||||
class="fill-height"
|
||||
>
|
||||
<v-tabs-items
|
||||
:key=" '' +
|
||||
creature.settings.hideSpellsTab +
|
||||
creature.settings.showTreeTab
|
||||
"
|
||||
:value="$store.getters.tabById($route.params.id)"
|
||||
class="card-background"
|
||||
@change="e => $store.commit(
|
||||
@@ -51,13 +55,17 @@
|
||||
<v-tab-item>
|
||||
<inventory-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item v-show="!creature.settings.hideSpellsTab">
|
||||
<v-tab-item
|
||||
v-if="!creature.settings.hideSpellsTab"
|
||||
>
|
||||
<spells-tab :creature-id="creatureId" />
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<character-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>
|
||||
|
||||
@@ -31,22 +31,11 @@
|
||||
:key="type"
|
||||
color="primary"
|
||||
:data-id="`insert-creature-property-type-${type}`"
|
||||
:label="type ? 'New ' + properties[type].name : 'New Property'"
|
||||
:label="getPropertyLabel(type)"
|
||||
:icon="type ? properties[type].icon : 'mdi-plus'"
|
||||
:disabled="!editPermission"
|
||||
@click="addProperty(type)"
|
||||
/>
|
||||
<template v-if="tabNumber === 5">
|
||||
<labeled-fab
|
||||
key="add-property"
|
||||
color="primary"
|
||||
data-id="add-creature-property-btn"
|
||||
label="Add Property"
|
||||
icon="mdi-plus"
|
||||
:disabled="!editPermission"
|
||||
@click="addProperty"
|
||||
/>
|
||||
</template>
|
||||
</v-speed-dial>
|
||||
</template>
|
||||
|
||||
@@ -55,6 +44,7 @@
|
||||
import { getHighestOrder } from '/imports/api/parenting/order.js';
|
||||
import insertProperty from '/imports/api/creature/creatureProperties/methods/insertProperty.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
@@ -130,13 +120,17 @@
|
||||
return this.$route.params.id;
|
||||
},
|
||||
tabNumber(){
|
||||
return this.$store.getters.tabById(this.creatureId);
|
||||
let tabNumber = this.$store.getters.tabById(this.creatureId);
|
||||
if (this.hideSpellsTab && tabNumber > 2){
|
||||
tabNumber += 1;
|
||||
}
|
||||
return tabNumber;
|
||||
},
|
||||
speedDials(){
|
||||
return this.speedDialsByTab[tabs[this.tabNumber]];
|
||||
},
|
||||
speedDialsByTab() { return {
|
||||
'stats': ['attribute', 'skill', 'action', 'attack'],
|
||||
'stats': ['attribute', 'skill', 'action', 'attack', 'buff'],
|
||||
'features': ['feature'],
|
||||
'inventory': ['item', 'container'],
|
||||
'spells': ['spellList', 'spell'],
|
||||
@@ -147,7 +141,17 @@
|
||||
return PROPERTIES;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
hideSpellsTab(){
|
||||
let creature = Creatures.findOne(this.creatureId);
|
||||
return creature?.settings.hideSpellsTab;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPropertyLabel(type){
|
||||
if (type === 'buff') return 'Buff or Condition';
|
||||
return type ? PROPERTIES[type].name : 'Property'
|
||||
},
|
||||
addProperty(forcedType){
|
||||
let creatureId = this.creatureId;
|
||||
let fab = hideFab();
|
||||
|
||||
@@ -80,6 +80,10 @@
|
||||
>
|
||||
<v-tabs
|
||||
v-if="creature && creature.settings"
|
||||
:key=" '' +
|
||||
creature.settings.hideSpellsTab +
|
||||
creature.settings.showTreeTab
|
||||
"
|
||||
class="flex"
|
||||
style="min-width: 0"
|
||||
centered
|
||||
@@ -102,7 +106,7 @@
|
||||
<v-tab>
|
||||
Inventory
|
||||
</v-tab>
|
||||
<v-tab v-show="!creature.settings.hideSpellsTab">
|
||||
<v-tab v-if="!creature.settings.hideSpellsTab">
|
||||
Spells
|
||||
</v-tab>
|
||||
<v-tab>
|
||||
@@ -134,13 +138,13 @@ import getThemeColor from '/imports/ui/utility/getThemeColor.js';
|
||||
import SharedIcon from '/imports/ui/components/SharedIcon.vue';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
components: {
|
||||
CharacterSheetFab,
|
||||
SharedIcon,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
computed: {
|
||||
creatureId(){
|
||||
return this.$route.params.id;
|
||||
|
||||
@@ -8,145 +8,173 @@
|
||||
{{ model.name }}
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<text-field
|
||||
<v-text-field
|
||||
v-model="searchInput"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
regular
|
||||
clearable
|
||||
hide-details
|
||||
:value="searchValue"
|
||||
:debounce="300"
|
||||
@change="searchChanged"
|
||||
@keyup.enter="insert"
|
||||
class="flex-grow-0"
|
||||
style="flex-basis: 300px;"
|
||||
:loading="searchLoading"
|
||||
@change="searchValue = searchInput || undefined"
|
||||
@click:clear="searchValue = undefined"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
class="library-nodes"
|
||||
<property-description
|
||||
:string="model.description"
|
||||
/>
|
||||
<p>
|
||||
{{ slotPropertyTypeName }} with tags:
|
||||
<template v-for="(tags, index) in tagsSearched.or">
|
||||
<property-tags
|
||||
:key="index"
|
||||
:tags="tags"
|
||||
:prefix="index ? 'OR' : undefined"
|
||||
/>
|
||||
</template>
|
||||
<template v-for="(tags, index) in tagsSearched.not">
|
||||
<property-tags
|
||||
:key="index"
|
||||
:tags="tags"
|
||||
prefix="NOT"
|
||||
/>
|
||||
</template>
|
||||
</p>
|
||||
<v-expansion-panels
|
||||
multiple
|
||||
inset
|
||||
>
|
||||
<v-fade-transition mode="out-in">
|
||||
<div v-if="libraryNodes && libraryNodes.length">
|
||||
<section
|
||||
class="layout wrap justify-between"
|
||||
>
|
||||
<v-card
|
||||
v-for="node in libraryNodes"
|
||||
:key="node._id"
|
||||
hover
|
||||
ripple
|
||||
class="slot-card layout column justify-end"
|
||||
:class="{'selected': node._id === (selectedNode && selectedNode._id)}"
|
||||
:dark="node._id === (selectedNode && selectedNode._id)"
|
||||
@click="selectedNode = node"
|
||||
>
|
||||
<v-img
|
||||
v-if="node.picture"
|
||||
:src="node.picture"
|
||||
:height="200"
|
||||
contain
|
||||
class="slot-card-image"
|
||||
/>
|
||||
<v-card-title primary-title>
|
||||
<tree-node-view
|
||||
class="mr-2 text-h6 mb-0"
|
||||
:class="{'theme--dark': node._id === (selectedNode && selectedNode._id)}"
|
||||
:hide-icon="node.picture"
|
||||
:model="node"
|
||||
:color="node.color"
|
||||
/>
|
||||
</v-card-title>
|
||||
<v-card-text
|
||||
v-if="node.description"
|
||||
class="pt-0"
|
||||
<template v-for="libraryNode in libraryNodes">
|
||||
<v-expansion-panel
|
||||
v-if="showDisabled || !libraryNode._disabledBySlotFillerCondition"
|
||||
:key="libraryNode._id"
|
||||
:model="libraryNode"
|
||||
:data-id="libraryNode._id"
|
||||
:class="{disabled: isDisabled(libraryNode)}"
|
||||
>
|
||||
<v-expansion-panel-header>
|
||||
<template #default="{ open }">
|
||||
<v-layout
|
||||
align-center
|
||||
class="flex-grow-0 mr-2"
|
||||
>
|
||||
<property-description
|
||||
class="slot-card-text line-clamp"
|
||||
:string="node.description"
|
||||
<v-checkbox
|
||||
v-if="libraryNode._disabledByAlreadyAdded"
|
||||
class="my-0 py-0"
|
||||
hide-details
|
||||
:input-value="true"
|
||||
disabled
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="countAll"
|
||||
class="ma-4"
|
||||
>
|
||||
<h4 v-if="numFiltered">
|
||||
Requirements of {{ numFiltered }} library properties were not met.
|
||||
</h4>
|
||||
<h4 v-else>
|
||||
Nothing suitable was found in your libraries.
|
||||
</h4>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="$subReady.slotFillers"
|
||||
class="ma-4"
|
||||
>
|
||||
<h4>
|
||||
Nothing suitable was found in your libraries
|
||||
<span v-if="searchValue">
|
||||
matching "{{ searchValue }}"
|
||||
</span>
|
||||
</h4>
|
||||
<p>
|
||||
This slot requires a {{ slotPropertyTypeName }}
|
||||
<template v-if="model.slotTags.length == 1">
|
||||
with the tag <code>{{ model.slotTags[0] }}</code>,
|
||||
</template>
|
||||
<template v-else-if="model.slotTags.length > 1">
|
||||
with the following tags:
|
||||
<span
|
||||
v-for="(tag, index) in model.slotTags"
|
||||
:key="index"
|
||||
<v-checkbox
|
||||
v-else
|
||||
v-model="selectedNodeIds"
|
||||
class="my-0 py-0"
|
||||
hide-details
|
||||
:disabled="isDisabled(libraryNode)"
|
||||
:value="libraryNode._id"
|
||||
@click.stop
|
||||
/>
|
||||
</v-layout>
|
||||
<v-layout column>
|
||||
<v-layout align-center>
|
||||
<tree-node-view :model="libraryNode" />
|
||||
<div
|
||||
v-if="libraryNode._disabledBySlotFillerCondition"
|
||||
class="error--text text-no-wrap text-truncate"
|
||||
>
|
||||
{{ libraryNode.slotFillerCondition }}
|
||||
</div>
|
||||
</v-layout>
|
||||
<div class="text-caption text-no-wrap text-truncate">
|
||||
{{ libraryNames[libraryNode.ancestors[0].id ] }}
|
||||
</div>
|
||||
</v-layout>
|
||||
<div
|
||||
v-if="libraryNode.slotQuantityFilled !== undefined && libraryNode.slotQuantityFilled !== 1"
|
||||
class="text-overline flex-grow-0 text-no-wrap"
|
||||
:class="{
|
||||
'error--text': isDisabled(libraryNode) &&
|
||||
libraryNode._disabledByQuantityFilled
|
||||
}"
|
||||
>
|
||||
<code>{{ tag }}</code>,
|
||||
</span>
|
||||
{{ libraryNode.slotQuantityFilled }} slots
|
||||
</div>
|
||||
<template v-if="open">
|
||||
<v-btn
|
||||
icon
|
||||
class="flex-grow-0"
|
||||
@click.stop="openPropertyDetails(libraryNode._id)"
|
||||
>
|
||||
<v-icon>mdi-window-restore</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</template>
|
||||
<span v-if="model.spaceLeft">
|
||||
that fills less than {{ model.spaceLeft }} {{ model.spaceLeft == 1 && 'slot' || 'slots' }}
|
||||
</span>
|
||||
</p>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<library-node-expansion-content :model="libraryNode" />
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</template>
|
||||
</v-expansion-panels>
|
||||
<v-layout
|
||||
v-if="(!$subReady.slotFillers && !searchValue) || currentLimit < countAll"
|
||||
column
|
||||
align-center
|
||||
justify-center
|
||||
class="ma-3"
|
||||
>
|
||||
<v-btn
|
||||
:loading="!$subReady.slotFillers"
|
||||
color="accent"
|
||||
@click="loadMore"
|
||||
>
|
||||
Load More
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
<template v-if="!showDisabled && disabledNodeCount">
|
||||
<v-layout
|
||||
column
|
||||
align-center
|
||||
justify-center
|
||||
class="ma-3"
|
||||
>
|
||||
<div>
|
||||
Requirements of {{ disabledNodeCount }} properties were not met
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
<v-fade-transition mode="out-in">
|
||||
<div
|
||||
v-if="!$subReady.slotFillers"
|
||||
key="character-loading"
|
||||
class="fill-height layout justify-center align-center"
|
||||
<v-btn
|
||||
class="mt-2"
|
||||
elevation="0"
|
||||
color="accent"
|
||||
@click="showDisabled = true"
|
||||
>
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="primary"
|
||||
size="64"
|
||||
/>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
<v-fade-transition mode="out-in">
|
||||
<div
|
||||
v-if="currentLimit < countAll"
|
||||
class="layout justify-center align-stretch"
|
||||
>
|
||||
<v-btn
|
||||
:loading="!$subReady.slotFillers"
|
||||
class="primary"
|
||||
@click="loadMore"
|
||||
>
|
||||
Load More
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
</div>
|
||||
Show All
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</template>
|
||||
<template slot="actions">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
text
|
||||
@click="$store.dispatch('popDialogStack')"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
text
|
||||
:disabled="!selectedNode"
|
||||
@click="insert"
|
||||
color="primary"
|
||||
:disabled="!dummySlot && !selectedNodeIds.length"
|
||||
@click="$store.dispatch('popDialogStack', selectedNodeIds)"
|
||||
>
|
||||
Insert
|
||||
<template v-if="model.spaceLeft">
|
||||
{{ totalQuantitySelected }} / {{ model.spaceLeft }}
|
||||
</template>
|
||||
<template v-if="slotId">
|
||||
Insert
|
||||
</template>
|
||||
<template v-else>
|
||||
Close Test
|
||||
</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
</dialog-base>
|
||||
@@ -157,127 +185,201 @@ import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue'
|
||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
||||
import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js'
|
||||
import Libraries from '/imports/api/library/Libraries.js';
|
||||
import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue';
|
||||
import PropertyTags from '/imports/ui/properties/viewers/shared/PropertyTags.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import { clone } from 'lodash';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
TreeNodeView,
|
||||
PropertyDescription,
|
||||
LibraryNodeExpansionContent,
|
||||
PropertyTags,
|
||||
},
|
||||
props:{
|
||||
slotId: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: undefined,
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: undefined,
|
||||
},
|
||||
dummySlot: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
selectedNode: undefined,
|
||||
selectedNodeIds: [],
|
||||
searchInput: undefined,
|
||||
searchValue: undefined,
|
||||
numFiltered: 0,
|
||||
showDisabled: false,
|
||||
disabledNodeCount: undefined,
|
||||
}},
|
||||
computed: {
|
||||
slotPropertyTypeName(){
|
||||
if (!this.model) return;
|
||||
if (!this.model.slotType) return 'property';
|
||||
let propName = getPropertyName(this.model.slotType);
|
||||
return propName && propName.toLowerCase();
|
||||
},
|
||||
},
|
||||
reactiveProvide: {
|
||||
name: 'context',
|
||||
include: ['creatureId'],
|
||||
},
|
||||
methods:{
|
||||
getTitle(model){
|
||||
if (!model) return;
|
||||
if (model.name) return model.name;
|
||||
let prop = PROPERTIES[model.type]
|
||||
return prop && prop.name;
|
||||
computed: {
|
||||
tagsSearched(){
|
||||
let or = [];
|
||||
let not = [];
|
||||
if (this.model.slotTags && this.model.slotTags.length){
|
||||
or.push(this.model.slotTags);
|
||||
}
|
||||
this.model.extraTags?.forEach(extras => {
|
||||
if (extras.tags?.length){
|
||||
if(extras.operation === 'OR'){
|
||||
or.push(extras.tags);
|
||||
} else if (extras.operation === 'NOT'){
|
||||
not.push(extras.tags);
|
||||
}
|
||||
}
|
||||
});
|
||||
return {or, not};
|
||||
},
|
||||
searchChanged(val, ack){
|
||||
this._subs['slotFillers'].setData('searchTerm', val);
|
||||
this._subs['slotFillers'].setData('limit', undefined);
|
||||
this.selectedNode = undefined;
|
||||
this.searchValue = val;
|
||||
setTimeout(ack, 200);
|
||||
slotPropertyTypeName(){
|
||||
if (!this.model) return;
|
||||
if (!this.model.slotType) return 'Property';
|
||||
let propName = getPropertyName(this.model.slotType);
|
||||
return propName;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
loadMore(){
|
||||
if (this.currentLimit >= this.countAll) return;
|
||||
this._subs['slotFillers'].setData('limit', this.currentLimit + 20);
|
||||
this._subs['slotFillers'].setData('limit', this.currentLimit + 50);
|
||||
},
|
||||
openPropertyDetails(id){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'library-node-dialog',
|
||||
elementId: id,
|
||||
data: {
|
||||
_id: id,
|
||||
},
|
||||
});
|
||||
},
|
||||
isDisabled(node){
|
||||
return node._disabledBySlotFillerCondition ||
|
||||
node._disabledByAlreadyAdded ||
|
||||
(
|
||||
node._disabledByQuantityFilled &&
|
||||
!this.selectedNodeIds.includes(node._id)
|
||||
)
|
||||
},
|
||||
insert(){
|
||||
if (!this.selectedNode) return;
|
||||
this.$store.dispatch('popDialogStack', this.selectedNode);
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
$subscribe: {
|
||||
'slotFillers'(){
|
||||
return [this.slotId]
|
||||
return [this.slotId, this.searchValue || undefined]
|
||||
},
|
||||
},
|
||||
searchLoading(){
|
||||
return !!this.searchValue && !this.$subReady.slotFillers;
|
||||
},
|
||||
model(){
|
||||
return CreatureProperties.findOne(this.slotId);
|
||||
if (this.slotId){
|
||||
return CreatureProperties.findOne(this.slotId);
|
||||
} else if (this.dummySlot) {
|
||||
let model = clone(this.dummySlot)
|
||||
model.quantityExpectedResult = +model.quantityExpected;
|
||||
model.spaceLeft = model.quantityExpectedResult;
|
||||
return model;
|
||||
}
|
||||
},
|
||||
creature(){
|
||||
if (!this.creatureId) return {variables: {}};
|
||||
return Creatures.findOne(this.creatureId);
|
||||
},
|
||||
currentLimit(){
|
||||
return this._subs['slotFillers'].data('limit') || 20;
|
||||
return this._subs['slotFillers'].data('limit') || 50;
|
||||
},
|
||||
countAll(){
|
||||
return this._subs['slotFillers'].data('countAll');
|
||||
},
|
||||
libraryNodes(){
|
||||
let filter = {
|
||||
alreadyAdded(){
|
||||
let added = new Set();
|
||||
if (!this.model.unique) return added;
|
||||
let ancestorId;
|
||||
if (this.model.unique === 'uniqueInSlot'){
|
||||
ancestorId = this.model._id;
|
||||
} else if (this.model.unique === 'uniqueInCreature'){
|
||||
ancestorId = this.creatureId;
|
||||
}
|
||||
CreatureProperties.find({
|
||||
'ancestors.id': ancestorId,
|
||||
libraryNodeId: {$exists: true},
|
||||
removed: {$ne: true},
|
||||
};
|
||||
if (this.model.slotTags && this.model.slotTags.length){
|
||||
filter.tags = {$all: this.model.slotTags};
|
||||
}
|
||||
if (this.model.slotType){
|
||||
filter.$or = [{
|
||||
type: this.model.slotType
|
||||
},{
|
||||
type: 'slotFiller',
|
||||
slotFillerType: this.model.slotType,
|
||||
}];
|
||||
}
|
||||
}, {
|
||||
fields: {libraryNodeId: 1},
|
||||
}).forEach(prop => {
|
||||
added.add(prop.libraryNodeId);
|
||||
});
|
||||
return added;
|
||||
},
|
||||
totalQuantitySelected(){
|
||||
let quantitySelected = 0;
|
||||
LibraryNodes.find({
|
||||
_id: {$in: this.selectedNodeIds}
|
||||
}, {
|
||||
fields: {slotQuantityFilled: 1},
|
||||
}).forEach(node => {
|
||||
if (Number.isFinite(node.slotQuantityFilled)){
|
||||
quantitySelected += node.slotQuantityFilled;
|
||||
} else {
|
||||
quantitySelected += 1;
|
||||
}
|
||||
});
|
||||
return quantitySelected;
|
||||
},
|
||||
spaceLeft(){
|
||||
if (this.model.quantityExpectedResult === 0) return undefined;
|
||||
return this.model.spaceLeft - this.totalQuantitySelected;
|
||||
},
|
||||
libraryNames(){
|
||||
let names = {};
|
||||
Libraries.find().forEach(lib => names[lib._id] = lib.name)
|
||||
return names;
|
||||
},
|
||||
libraryNodes(){
|
||||
let filter = getSlotFillFilter({slot: this.model});
|
||||
let nodes = LibraryNodes.find(filter, {
|
||||
sort: {name: 1, order: 1}
|
||||
}).fetch();
|
||||
let totalNodes = nodes.length;
|
||||
// Filter out slotFillers whose condition isn't met or are too big to fit
|
||||
let disabledNodeCount = 0;
|
||||
// Mark slotFillers whose condition isn't met or are too big to fit
|
||||
// the quantity to fill
|
||||
nodes = nodes.filter(node => {
|
||||
nodes.forEach(node => {
|
||||
if (node.slotFillerCondition){
|
||||
let {result} = evaluateString({
|
||||
string: node.slotFillerCondition,
|
||||
scope: this.creature.variables,
|
||||
fn: 'reduce',
|
||||
});
|
||||
if (!result.value) return false;
|
||||
if (!result.value){
|
||||
node._disabledBySlotFillerCondition = true;
|
||||
disabledNodeCount += 1;
|
||||
}
|
||||
}
|
||||
let quantityToFill = node.type === 'slotFiller' ? node.slotQuantityFilled : 1;
|
||||
if (
|
||||
node.type === 'slotFiller' &&
|
||||
this.model.spaceLeft > 0 &&
|
||||
node.slotQuantityFilled > this.model.spaceLeft
|
||||
quantityToFill > this.spaceLeft
|
||||
){
|
||||
return false;
|
||||
node._disabledByQuantityFilled = true;
|
||||
}
|
||||
if (this.alreadyAdded.has(node._id)){
|
||||
node._disabledByAlreadyAdded = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.numFiltered = totalNodes - nodes.length;
|
||||
if (nodes.length === 1) this.selectedNode = nodes[0];
|
||||
this.disabledNodeCount = disabledNodeCount;
|
||||
return nodes;
|
||||
},
|
||||
}
|
||||
@@ -285,17 +387,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.slot-card {
|
||||
max-width: 500px;
|
||||
width: 300px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
margin: 4px;
|
||||
}
|
||||
.slot-card-text.line-clamp {
|
||||
-webkit-line-clamp: 5;
|
||||
}
|
||||
.slot-card.selected {
|
||||
background: #8E1B1B;
|
||||
.disabled {
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -90,10 +90,10 @@ export default {
|
||||
slotId,
|
||||
creatureId,
|
||||
},
|
||||
callback(node){
|
||||
if(!node) return;
|
||||
callback(nodeIds){
|
||||
if (!nodeIds || !nodeIds.length) return;
|
||||
let newPropertyId = insertPropertyFromLibraryNode.call({
|
||||
nodeIds: [node._id],
|
||||
nodeIds,
|
||||
parentRef: {
|
||||
'id': slotId,
|
||||
'collection': 'creatureProperties',
|
||||
|
||||
@@ -21,6 +21,7 @@ import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
||||
import SlotDetailsDialog from '/imports/ui/creature/slots/SlotDetailsDialog.vue';
|
||||
import SlotFillDialog from '/imports/ui/creature/slots/SlotFillDialog.vue';
|
||||
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||
import TransferOwnershipDialog from '/imports/ui/sharing/TransferOwnershipDialog.vue';
|
||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||
|
||||
export default {
|
||||
@@ -47,5 +48,6 @@ export default {
|
||||
SlotDetailsDialog,
|
||||
SlotFillDialog,
|
||||
TierTooLowDialog,
|
||||
TransferOwnershipDialog,
|
||||
UsernameDialog,
|
||||
};
|
||||
|
||||
@@ -66,9 +66,10 @@
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureFolders from '/imports/api/creature/creatureFolders/CreatureFolders.js';
|
||||
import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolderList.vue';
|
||||
import getCreatureUrlName from '/imports/api/creature/creatures/getCreatureUrlName.js';
|
||||
|
||||
const characterTransform = function(char){
|
||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||
char.url = `/character/${char._id}/${getCreatureUrlName(char)}`;
|
||||
char.initial = char.name && char.name[0] || '?';
|
||||
return char;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
class="property-viewer"
|
||||
/>
|
||||
<tree-node-list
|
||||
v-if="$subReady.descendantLibraryNodes"
|
||||
group="library-node-expansion"
|
||||
:children="propertyChildren"
|
||||
@selected="clickChild"
|
||||
|
||||
@@ -82,9 +82,10 @@
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import CreatureFolderList from '/imports/ui/creature/creatureList/CreatureFolderList.vue';
|
||||
import ArchiveButton from '/imports/ui/creature/creatureList/ArchiveButton.vue';
|
||||
import getCreatureUrlName from '/imports/api/creature/creatures/getCreatureUrlName.js';
|
||||
|
||||
const characterTransform = function(char){
|
||||
char.url = `/character/${char._id}/${char.urlName || '-'}`;
|
||||
char.url = `/character/${char._id}/${getCreatureUrlName(char)}`;
|
||||
char.initial = char.name && char.name[0] || '?';
|
||||
return char;
|
||||
};
|
||||
|
||||
@@ -80,12 +80,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.skill-list-tile >>> .v-list__tile {
|
||||
height: 34px;
|
||||
}
|
||||
.skill-list-tile{
|
||||
background: inherit;
|
||||
}
|
||||
.prof-icon {
|
||||
min-width: 30px;
|
||||
}
|
||||
@@ -95,7 +89,3 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
$list-item-min-height: 32px;
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,25 @@
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Applied"
|
||||
class="mt-0"
|
||||
:value="model.applied"
|
||||
:error-messages="errors.applied"
|
||||
@change="change('applied', ...arguments)"
|
||||
/>
|
||||
<v-expand-transition>
|
||||
<div v-if="model.applied">
|
||||
<v-alert
|
||||
type="info"
|
||||
outlined
|
||||
>
|
||||
When buffs are applied they become active on a creature.
|
||||
Turn this off if the buff needs to be applied to a target by an action
|
||||
or spell.
|
||||
</v-alert>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
<text-area
|
||||
label="Description"
|
||||
:value="model.description"
|
||||
@@ -24,15 +43,18 @@
|
||||
@change="change('duration', ...arguments)"
|
||||
/>
|
||||
-->
|
||||
<smart-select
|
||||
label="Target"
|
||||
:hint="targetOptionHint"
|
||||
:items="targetOptions"
|
||||
:value="model.target"
|
||||
:error-messages="errors.target"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
<v-expand-transition>
|
||||
<smart-select
|
||||
v-if="!model.applied"
|
||||
label="Target"
|
||||
:hint="targetOptionHint"
|
||||
:items="targetOptions"
|
||||
:value="model.target"
|
||||
:error-messages="errors.target"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
</v-expand-transition>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
|
||||
@@ -17,16 +17,63 @@
|
||||
:error-messages="errors.slotType"
|
||||
@change="change('slotType', ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Tags Required"
|
||||
hint="The slot must be filled with a property which has all the listed tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="model.slotTags"
|
||||
:error-messages="errors.slotTags"
|
||||
@change="change('slotTags', ...arguments)"
|
||||
/>
|
||||
<v-layout align-center>
|
||||
<v-btn
|
||||
icon
|
||||
style="margin-top: -30px;"
|
||||
class="mr-2"
|
||||
:loading="addExtraTagsLoading"
|
||||
:disabled="extraTagsFull"
|
||||
@click="addExtraTags"
|
||||
>
|
||||
<v-icon>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<smart-combobox
|
||||
label="Tags Required"
|
||||
hint="The slot must be filled with a property which has all the listed tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="model.slotTags"
|
||||
:error-messages="errors.slotTags"
|
||||
@change="change('slotTags', ...arguments)"
|
||||
/>
|
||||
</v-layout>
|
||||
<v-slide-x-transition group>
|
||||
<div
|
||||
v-for="(extras, i) in model.extraTags"
|
||||
:key="extras._id"
|
||||
class="extra-tags layout align-center justify-space-between"
|
||||
>
|
||||
<smart-select
|
||||
label="Operation"
|
||||
style="width: 90px; flex-grow: 0;"
|
||||
:items="extraTagOperations"
|
||||
:value="extras.operation"
|
||||
:error-messages="errors.extraTags && errors.extraTags[i]"
|
||||
@change="change(['extraTags', i, 'operation'], ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
:hint="extras.operation === 'OR' ? 'The slot can be filled with a property that has all of these tags instead' : 'The slot cannot be filled with a property that has any of these tags'"
|
||||
class="mx-2"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:value="extras.tags"
|
||||
@change="change(['extraTags', i, 'tags'], ...arguments)"
|
||||
/>
|
||||
<v-btn
|
||||
icon
|
||||
style="margin-top: -30px;"
|
||||
@click="$emit('pull', {path: ['extraTags', i]})"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
<text-field
|
||||
label="Quantity"
|
||||
hint="How many matching properties must be used to fill this slot, 0 is unlimited"
|
||||
@@ -45,6 +92,29 @@
|
||||
/>
|
||||
<calculation-error-list :errors="model.slotConditionErrors" />
|
||||
|
||||
<smart-select
|
||||
label="Unique"
|
||||
style="flex-basis: 300px;"
|
||||
clearable
|
||||
hint="Do the properties that fill this slot need to be unique?"
|
||||
:items="uniqueOptions"
|
||||
:value="model.unique"
|
||||
:error-messages="errors.unique"
|
||||
@change="change('unique', ...arguments)"
|
||||
/>
|
||||
|
||||
<v-layout justify-center>
|
||||
<v-btn
|
||||
v-if="context.isLibraryForm"
|
||||
color="accent"
|
||||
class="ma-2 mb-4"
|
||||
data-id="test-slot-button"
|
||||
@click="testSlot"
|
||||
>
|
||||
Test Slot
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
|
||||
<text-area
|
||||
label="Description"
|
||||
:value="model.description"
|
||||
@@ -77,6 +147,7 @@
|
||||
</div>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
hint="This slot's own tags which will be used to fill other slots"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
@@ -92,6 +163,7 @@
|
||||
import FormSection from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
|
||||
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
||||
import { SlotSchema } from '/imports/api/properties/Slots.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -99,12 +171,60 @@
|
||||
CalculationErrorList,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
data(){
|
||||
let slotTypes = [];
|
||||
for (let key in PROPERTIES){
|
||||
slotTypes.push({text: PROPERTIES[key].name, value: key});
|
||||
}
|
||||
return {slotTypes};
|
||||
return {
|
||||
slotTypes,
|
||||
addExtraTagsLoading: false,
|
||||
extraTagOperations: ['OR', 'NOT'],
|
||||
uniqueOptions: [{
|
||||
text: 'Each property inside this slot should be unique',
|
||||
value: 'uniqueInSlot',
|
||||
}, {
|
||||
text: 'Properties in this slot should be unique accross the whole character',
|
||||
value: 'uniqueInCreature',
|
||||
}],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
extraTagsFull(){
|
||||
if (!this.model.extraTags) return false;
|
||||
let maxCount = SlotSchema.get('extraTags', 'maxCount');
|
||||
return this.model.extraTags.length >= maxCount;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
acknowledgeAddResult(){
|
||||
this.addExtraTagsLoading = false;
|
||||
},
|
||||
addExtraTags(){
|
||||
this.addExtraTagsLoading = true;
|
||||
this.$emit('push', {
|
||||
path: ['extraTags'],
|
||||
value: {
|
||||
_id: Random.id(),
|
||||
operation: 'OR',
|
||||
tags: [],
|
||||
},
|
||||
ack: this.acknowledgeAddResult,
|
||||
});
|
||||
},
|
||||
testSlot(){
|
||||
if (!this.context.isLibraryForm) return;
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'slot-fill-dialog',
|
||||
elementId: 'test-slot-button',
|
||||
data: {
|
||||
dummySlot: this.model,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
title(){
|
||||
let model = this.model;
|
||||
if (!model) return;
|
||||
if (model.quantity !== 1){
|
||||
if (Number.isFinite(model.quantity) && model.quantity !== 1){
|
||||
if (model.plural){
|
||||
return `${model.quantity} ${model.plural}`;
|
||||
} else if (model.name){
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
<script lang="js">
|
||||
import treeNodeViewMixin from '/imports/ui/properties/treeNodeViews/treeNodeViewMixin.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeView,
|
||||
},
|
||||
name: 'ReferenceTreeNode',
|
||||
mixins: [treeNodeViewMixin],
|
||||
beforeCreate () {
|
||||
this.$options.components.TreeNodeView = require('/imports/ui/properties/treeNodeViews/TreeNodeView.vue').default
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import treeNodeViewIndex from '/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js';
|
||||
|
||||
export default {
|
||||
name: 'TreeNodeView',
|
||||
components: {
|
||||
...treeNodeViewIndex
|
||||
},
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
v-if="tagString"
|
||||
v-if="tags.length"
|
||||
class="tags"
|
||||
:class="{'ma-3': !noMargin}"
|
||||
:class="{'ma-2': !noMargin}"
|
||||
>
|
||||
{{ tagString }}
|
||||
<span
|
||||
v-if="prefix"
|
||||
class="mx-1 text-overline"
|
||||
>
|
||||
{{ prefix }}
|
||||
</span>
|
||||
<v-chip
|
||||
v-for="(tag, i) in tags"
|
||||
:key="tag + i"
|
||||
class="mx-1"
|
||||
>
|
||||
{{ tag }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,17 +28,13 @@ export default {
|
||||
default: () => [],
|
||||
},
|
||||
noMargin: Boolean,
|
||||
prefix: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
tagString(){
|
||||
return this.tags.join(', ');
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.tags {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -130,18 +130,9 @@ RouterFactory.configure(factory => {
|
||||
meta: {
|
||||
title: 'Library',
|
||||
},
|
||||
},{
|
||||
path: '/character/:id/:urlName',
|
||||
components: {
|
||||
default: CharacterSheetPage,
|
||||
toolbar: CharacterSheetToolbar,
|
||||
rightDrawer: CharacterSheetRightDrawer,
|
||||
},
|
||||
meta: {
|
||||
title: 'Character Sheet',
|
||||
},
|
||||
},{
|
||||
path: '/character/:id',
|
||||
alias: '/character/:id/:urlName',
|
||||
components: {
|
||||
default: CharacterSheetPage,
|
||||
toolbar: CharacterSheetToolbar,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
v-if="model.public && docRef.collection === 'libraries'"
|
||||
readonly
|
||||
label="Link"
|
||||
:value="'https://beta.dicecloud.com' + this.$router.resolve({
|
||||
:value="'https://beta.dicecloud.com' + $router.resolve({
|
||||
name: 'singleLibrary',
|
||||
params: { id: model._id },
|
||||
}).href"
|
||||
@@ -56,6 +56,7 @@
|
||||
<v-menu
|
||||
bottom
|
||||
left
|
||||
:data-id="'menu-' + user._id"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
@@ -84,6 +85,15 @@
|
||||
</v-list-item-action>
|
||||
<v-list-item-title>View only</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-if="user.permission === 'writer'"
|
||||
@click="makeOwner(user)"
|
||||
>
|
||||
<v-list-item-action>
|
||||
<v-icon>mdi-signature</v-icon>
|
||||
</v-list-item-action>
|
||||
<v-list-item-title>Transfer Onwership</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="updateSharing(user._id, 'none')">
|
||||
<v-list-item-action>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
@@ -181,6 +191,16 @@ export default {
|
||||
userId,
|
||||
role,
|
||||
});
|
||||
},
|
||||
makeOwner(user){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'transfer-ownership-dialog',
|
||||
elementId: 'menu-' + user._id,
|
||||
data: {
|
||||
docRef: this.docRef,
|
||||
user,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
|
||||
79
app/imports/ui/sharing/TransferOwnershipDialog.vue
Normal file
79
app/imports/ui/sharing/TransferOwnershipDialog.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template lang="html">
|
||||
<dialog-base>
|
||||
<v-toolbar-title slot="toolbar">
|
||||
Transfer Ownership
|
||||
</v-toolbar-title>
|
||||
<v-alert
|
||||
type="error"
|
||||
outlined
|
||||
>
|
||||
<template v-if="error">
|
||||
<p>
|
||||
{{ error }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>
|
||||
Are you sure you want to transfer ownership to {{ user.username || user._id }}?
|
||||
</p><p>
|
||||
This can only be undone by the user you are transferring ownership to.
|
||||
</p><p>
|
||||
You will still have edit permission.
|
||||
</p>
|
||||
</template>
|
||||
</v-alert>
|
||||
<v-layout justify-center>
|
||||
<v-btn
|
||||
color="accent"
|
||||
@click="transfer"
|
||||
>
|
||||
Transfer
|
||||
<template v-if="user.username">
|
||||
to {{ user.username }}
|
||||
</template>
|
||||
</v-btn>
|
||||
</v-layout>
|
||||
</dialog-base>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import { transferOwnership } from '/imports/api/sharing/sharing.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
},
|
||||
props: {
|
||||
docRef: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
user: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
error: undefined,
|
||||
}},
|
||||
methods: {
|
||||
transfer(){
|
||||
transferOwnership.call({
|
||||
docRef: this.docRef,
|
||||
userId: this.user._id
|
||||
}, error => {
|
||||
if (!error){
|
||||
this.error = undefined;
|
||||
this.$store.dispatch('popDialogStack')
|
||||
return;
|
||||
}
|
||||
this.error = error.reason || error.message || error.toString();
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
23
app/package-lock.json
generated
23
app/package-lock.json
generated
@@ -2330,6 +2330,11 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"mongo-object": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-0.1.4.tgz",
|
||||
@@ -2805,6 +2810,11 @@
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"speakingurl": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
|
||||
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
@@ -2956,9 +2966,9 @@
|
||||
}
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
|
||||
"integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
|
||||
"version": "6.1.6",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz",
|
||||
"integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==",
|
||||
"requires": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
@@ -2966,13 +2976,6 @@
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"request": "^2.88.2",
|
||||
"simpl-schema": "^1.12.0",
|
||||
"source-map-support": "^0.5.16",
|
||||
"speakingurl": "^14.0.1",
|
||||
"styles": "^0.2.1",
|
||||
"underscore": "^1.13.1",
|
||||
"vue": "2.6.10",
|
||||
|
||||
Reference in New Issue
Block a user