Hunted the last of the \t's to extinction
This commit is contained in:
@@ -4,33 +4,33 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
let CreatureFolders = new Mongo.Collection('creatureFolders');
|
||||
|
||||
let creatureFolderSchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
creatures: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
'creatures.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
},
|
||||
archived: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
creatures: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
},
|
||||
'creatures.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
},
|
||||
archived: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
},
|
||||
});
|
||||
|
||||
CreatureFolders.attachSchema(creatureFolderSchema);
|
||||
|
||||
@@ -2,49 +2,49 @@ 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: {
|
||||
type: Date,
|
||||
autoValue: function () {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
},
|
||||
// The date in-world of this event
|
||||
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: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
}
|
||||
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: {
|
||||
type: Date,
|
||||
autoValue: function () {
|
||||
// If the date isn't set, set it to now
|
||||
if (!this.isSet) {
|
||||
return new Date();
|
||||
}
|
||||
},
|
||||
},
|
||||
// The date in-world of this event
|
||||
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: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
}
|
||||
});
|
||||
|
||||
export { ExperienceSchema };
|
||||
|
||||
@@ -20,15 +20,15 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
let Libraries = new Mongo.Collection('libraries');
|
||||
|
||||
let LibrarySchema = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.summary,
|
||||
},
|
||||
});
|
||||
|
||||
LibrarySchema.extend(SharingSchema);
|
||||
@@ -38,96 +38,96 @@ Libraries.attachSchema(LibrarySchema);
|
||||
export default Libraries;
|
||||
|
||||
const insertLibrary = new ValidatedMethod({
|
||||
name: 'libraries.insert',
|
||||
mixins: [
|
||||
simpleSchemaMixin,
|
||||
],
|
||||
schema: LibrarySchema.omit('owner'),
|
||||
run(library) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
'You need to be logged in to insert a library');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
`The ${tier.name} tier does not allow you to insert a library`);
|
||||
}
|
||||
library.owner = this.userId;
|
||||
return Libraries.insert(library);
|
||||
},
|
||||
name: 'libraries.insert',
|
||||
mixins: [
|
||||
simpleSchemaMixin,
|
||||
],
|
||||
schema: LibrarySchema.omit('owner'),
|
||||
run(library) {
|
||||
if (!this.userId) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
'You need to be logged in to insert a library');
|
||||
}
|
||||
let tier = getUserTier(this.userId);
|
||||
if (!tier.paidBenefits) {
|
||||
throw new Meteor.Error('Libraries.methods.insert.denied',
|
||||
`The ${tier.name} tier does not allow you to insert a library`);
|
||||
}
|
||||
library.owner = this.userId;
|
||||
return Libraries.insert(library);
|
||||
},
|
||||
});
|
||||
|
||||
const updateLibraryName = new ValidatedMethod({
|
||||
name: 'libraries.updateName',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, name }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, { $set: { name } });
|
||||
},
|
||||
name: 'libraries.updateName',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, name }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, { $set: { name } });
|
||||
},
|
||||
});
|
||||
|
||||
const updateLibraryDescription = new ValidatedMethod({
|
||||
name: 'libraries.updateDescription',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, description }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, { $set: { description } });
|
||||
},
|
||||
name: 'libraries.updateDescription',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id, description }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertEditPermission(library, this.userId);
|
||||
Libraries.update(_id, { $set: { description } });
|
||||
},
|
||||
});
|
||||
|
||||
const removeLibrary = new ValidatedMethod({
|
||||
name: 'libraries.remove',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertOwnership(library, this.userId);
|
||||
this.unblock();
|
||||
removeLibaryWork(_id)
|
||||
}
|
||||
name: 'libraries.remove',
|
||||
validate: new SimpleSchema({
|
||||
_id: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.id
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ _id }) {
|
||||
let library = Libraries.findOne(_id);
|
||||
assertOwnership(library, this.userId);
|
||||
this.unblock();
|
||||
removeLibaryWork(_id)
|
||||
}
|
||||
});
|
||||
|
||||
export function removeLibaryWork(libraryId) {
|
||||
Libraries.remove(libraryId);
|
||||
LibraryNodes.remove({ 'ancestors.id': libraryId });
|
||||
Libraries.remove(libraryId);
|
||||
LibraryNodes.remove({ 'ancestors.id': libraryId });
|
||||
}
|
||||
|
||||
export { LibrarySchema, insertLibrary, updateLibraryName, updateLibraryDescription, removeLibrary };
|
||||
|
||||
@@ -3,79 +3,79 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let BuffSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
hideRemoveButton: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// How many rounds this buff lasts
|
||||
duration: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Prevent the children from being crystalized
|
||||
skipCrystalization: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
hideRemoveButton: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// How many rounds this buff lasts
|
||||
duration: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Prevent the children from being crystalized
|
||||
skipCrystalization: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyBuffSchema = createPropertySchema({
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
duration: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
durationSpent: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
min: 0,
|
||||
},
|
||||
appliedBy: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
},
|
||||
'appliedBy.name': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
'appliedBy.id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
'appliedBy.collection': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
},
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.description,
|
||||
},
|
||||
duration: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
durationSpent: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
min: 0,
|
||||
},
|
||||
appliedBy: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
},
|
||||
'appliedBy.name': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
'appliedBy.id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
'appliedBy.collection': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.collectionName,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedBuffSchema = new SimpleSchema()
|
||||
.extend(BuffSchema)
|
||||
.extend(ComputedOnlyBuffSchema);
|
||||
.extend(BuffSchema)
|
||||
.extend(ComputedOnlyBuffSchema);
|
||||
|
||||
export { BuffSchema, ComputedOnlyBuffSchema, ComputedBuffSchema };
|
||||
|
||||
@@ -3,69 +3,69 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let ContainerSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
carried: {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
optional: true,
|
||||
},
|
||||
contentsWeightless: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
carried: {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
optional: true,
|
||||
},
|
||||
contentsWeightless: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyContainerSchema = createPropertySchema({
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
// Weight of all the contents, zero if `contentsWeightless` is true
|
||||
contentsWeight: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Weight of all the carried contents (some sub-containers might not be carried)
|
||||
// zero if `contentsWeightless` is true
|
||||
carriedWeight: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
contentsValue: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
carriedValue: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
// Weight of all the contents, zero if `contentsWeightless` is true
|
||||
contentsWeight: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
// Weight of all the carried contents (some sub-containers might not be carried)
|
||||
// zero if `contentsWeightless` is true
|
||||
carriedWeight: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
contentsValue: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
carriedValue: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedContainerSchema = new SimpleSchema()
|
||||
.extend(ComputedOnlyContainerSchema)
|
||||
.extend(ContainerSchema);
|
||||
.extend(ComputedOnlyContainerSchema)
|
||||
.extend(ContainerSchema);
|
||||
|
||||
export { ContainerSchema, ComputedOnlyContainerSchema, ComputedContainerSchema };
|
||||
|
||||
@@ -3,69 +3,69 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
const ItemSchema = createPropertySchema({
|
||||
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: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// Number currently held
|
||||
quantity: {
|
||||
type: SimpleSchema.Integer,
|
||||
min: 0,
|
||||
defaultValue: 1
|
||||
},
|
||||
// Weight per item in the stack
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
// Value per item in the stack, in gold pieces
|
||||
value: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
// If this item is equipped, it requires attunement
|
||||
requiresAttunement: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
attuned: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Show increment/decrement buttons in item lists
|
||||
showIncrement: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Unequipped items shouldn't affect creature stats
|
||||
equipped: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
},
|
||||
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: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// Number currently held
|
||||
quantity: {
|
||||
type: SimpleSchema.Integer,
|
||||
min: 0,
|
||||
defaultValue: 1
|
||||
},
|
||||
// Weight per item in the stack
|
||||
weight: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
// Value per item in the stack, in gold pieces
|
||||
value: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
optional: true,
|
||||
},
|
||||
// If this item is equipped, it requires attunement
|
||||
requiresAttunement: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
attuned: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Show increment/decrement buttons in item lists
|
||||
showIncrement: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Unequipped items shouldn't affect creature stats
|
||||
equipped: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyItemSchema = createPropertySchema({
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedItemSchema = new SimpleSchema()
|
||||
.extend(ItemSchema)
|
||||
.extend(ComputedOnlyItemSchema);
|
||||
.extend(ItemSchema)
|
||||
.extend(ComputedOnlyItemSchema);
|
||||
|
||||
export { ItemSchema, ComputedItemSchema, ComputedOnlyItemSchema };
|
||||
|
||||
@@ -3,99 +3,99 @@ import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
const magicSchools = [
|
||||
'abjuration',
|
||||
'conjuration',
|
||||
'divination',
|
||||
'enchantment',
|
||||
'evocation',
|
||||
'illusion',
|
||||
'necromancy',
|
||||
'transmutation',
|
||||
'abjuration',
|
||||
'conjuration',
|
||||
'divination',
|
||||
'enchantment',
|
||||
'evocation',
|
||||
'illusion',
|
||||
'necromancy',
|
||||
'transmutation',
|
||||
];
|
||||
|
||||
let SpellSchema = new SimpleSchema({})
|
||||
.extend(ActionSchema)
|
||||
.extend({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// If it's always prepared, it doesn't count against the number of spells
|
||||
// prepared in a spell list, and enabled should be true
|
||||
alwaysPrepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
prepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// This spell ignores spell slot rules
|
||||
castWithoutSpellSlots: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
hasAttackRoll: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
castingTime: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'action',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
range: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
duration: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'Instantaneous',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
verbal: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
somatic: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
concentration: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
material: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
ritual: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
max: 9,
|
||||
min: 0,
|
||||
},
|
||||
school: {
|
||||
type: String,
|
||||
defaultValue: 'abjuration',
|
||||
allowedValues: magicSchools,
|
||||
},
|
||||
});
|
||||
.extend(ActionSchema)
|
||||
.extend({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// If it's always prepared, it doesn't count against the number of spells
|
||||
// prepared in a spell list, and enabled should be true
|
||||
alwaysPrepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
prepared: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// This spell ignores spell slot rules
|
||||
castWithoutSpellSlots: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
hasAttackRoll: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
castingTime: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'action',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
range: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
duration: {
|
||||
type: String,
|
||||
optional: true,
|
||||
defaultValue: 'Instantaneous',
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
verbal: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
somatic: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
concentration: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
material: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.spellDetail,
|
||||
},
|
||||
ritual: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
level: {
|
||||
type: SimpleSchema.Integer,
|
||||
defaultValue: 1,
|
||||
max: 9,
|
||||
min: 0,
|
||||
},
|
||||
school: {
|
||||
type: String,
|
||||
defaultValue: 'abjuration',
|
||||
allowedValues: magicSchools,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlySpellSchema = new SimpleSchema()
|
||||
.extend(ComputedOnlyActionSchema);
|
||||
.extend(ComputedOnlyActionSchema);
|
||||
|
||||
const ComputedSpellSchema = new SimpleSchema()
|
||||
.extend(SpellSchema)
|
||||
.extend(ComputedOnlySpellSchema);
|
||||
.extend(SpellSchema)
|
||||
.extend(ComputedOnlySpellSchema);
|
||||
|
||||
export { SpellSchema, ComputedOnlySpellSchema, ComputedSpellSchema };
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
const ColorSchema = new SimpleSchema({
|
||||
color: {
|
||||
type: String,
|
||||
// match hex colors of the form #A23 or #A23f56
|
||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||
optional: true,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
// match hex colors of the form #A23 or #A23f56
|
||||
regEx: /^#([a-f0-9]{3}){1,2}\b$/i,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
export default ColorSchema;
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
const DeathSavesSchema = new SimpleSchema({
|
||||
pass: {
|
||||
type: SimpleSchema.Integer,
|
||||
min: 0,
|
||||
max: 3,
|
||||
defaultValue: 0,
|
||||
},
|
||||
fail: {
|
||||
type: SimpleSchema.Integer,
|
||||
min: 0,
|
||||
max: 3,
|
||||
defaultValue: 0,
|
||||
},
|
||||
canDeathSave: {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
},
|
||||
stable: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
},
|
||||
pass: {
|
||||
type: SimpleSchema.Integer,
|
||||
min: 0,
|
||||
max: 3,
|
||||
defaultValue: 0,
|
||||
},
|
||||
fail: {
|
||||
type: SimpleSchema.Integer,
|
||||
min: 0,
|
||||
max: 3,
|
||||
defaultValue: 0,
|
||||
},
|
||||
canDeathSave: {
|
||||
type: Boolean,
|
||||
defaultValue: true,
|
||||
},
|
||||
stable: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default DeathSavesSchema;
|
||||
|
||||
@@ -3,36 +3,36 @@ import '/imports/api/sharing/sharing.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
|
||||
let SharingSchema = new SimpleSchema({
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1
|
||||
},
|
||||
readers: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
maxCount: STORAGE_LIMITS.readersCount,
|
||||
},
|
||||
'readers.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
writers: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
maxCount: STORAGE_LIMITS.writersCount,
|
||||
},
|
||||
'writers.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
public: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
index: 1,
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1
|
||||
},
|
||||
readers: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
maxCount: STORAGE_LIMITS.readersCount,
|
||||
},
|
||||
'readers.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
writers: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
index: 1,
|
||||
maxCount: STORAGE_LIMITS.writersCount,
|
||||
},
|
||||
'writers.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id
|
||||
},
|
||||
public: {
|
||||
type: Boolean,
|
||||
defaultValue: false,
|
||||
index: 1,
|
||||
},
|
||||
});
|
||||
|
||||
export default SharingSchema;
|
||||
|
||||
@@ -11,321 +11,321 @@ const defaultLibraries = process.env.DEFAULT_LIBRARIES && process.env.DEFAULT_LI
|
||||
const defaultLibraryCollections = process.env.DEFAULT_LIBRARY_COLLECTIONS && process.env.DEFAULT_LIBRARY_COLLECTIONS.split(',') || [];
|
||||
|
||||
const userSchema = new SimpleSchema({
|
||||
username: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: 30,
|
||||
min: 4,
|
||||
},
|
||||
emails: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'emails.$': {
|
||||
type: Object,
|
||||
},
|
||||
'emails.$.address': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Email,
|
||||
},
|
||||
'emails.$.verified': {
|
||||
type: Boolean,
|
||||
},
|
||||
registered_emails: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'registered_emails.$': {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date
|
||||
},
|
||||
services: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
roles: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'roles.$': {
|
||||
type: String
|
||||
},
|
||||
// In order to avoid an 'Exception in setInterval callback' from Meteor
|
||||
heartbeat: {
|
||||
type: Date,
|
||||
optional: true,
|
||||
},
|
||||
apiKey: {
|
||||
type: String,
|
||||
index: 1,
|
||||
optional: true,
|
||||
},
|
||||
darkMode: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
subscribedLibraries: {
|
||||
type: Array,
|
||||
defaultValue: defaultLibraries,
|
||||
maxCount: 100,
|
||||
},
|
||||
'subscribedLibraries.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedLibraryCollections: {
|
||||
type: Array,
|
||||
defaultValue: defaultLibraryCollections,
|
||||
maxCount: 100,
|
||||
},
|
||||
'subscribedLibraryCollections.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedCharacters: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
max: 100,
|
||||
},
|
||||
'subscribedCharacters.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
fileStorageUsed: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
preferences: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
'preferences.swapAbilityScoresAndModifiers': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
'preferences.hidePropertySelectDialogHelp': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
username: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: 30,
|
||||
min: 4,
|
||||
},
|
||||
emails: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'emails.$': {
|
||||
type: Object,
|
||||
},
|
||||
'emails.$.address': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Email,
|
||||
},
|
||||
'emails.$.verified': {
|
||||
type: Boolean,
|
||||
},
|
||||
registered_emails: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'registered_emails.$': {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
createdAt: {
|
||||
type: Date
|
||||
},
|
||||
services: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
roles: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'roles.$': {
|
||||
type: String
|
||||
},
|
||||
// In order to avoid an 'Exception in setInterval callback' from Meteor
|
||||
heartbeat: {
|
||||
type: Date,
|
||||
optional: true,
|
||||
},
|
||||
apiKey: {
|
||||
type: String,
|
||||
index: 1,
|
||||
optional: true,
|
||||
},
|
||||
darkMode: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
subscribedLibraries: {
|
||||
type: Array,
|
||||
defaultValue: defaultLibraries,
|
||||
maxCount: 100,
|
||||
},
|
||||
'subscribedLibraries.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedLibraryCollections: {
|
||||
type: Array,
|
||||
defaultValue: defaultLibraryCollections,
|
||||
maxCount: 100,
|
||||
},
|
||||
'subscribedLibraryCollections.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribedCharacters: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
max: 100,
|
||||
},
|
||||
'subscribedCharacters.$': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
fileStorageUsed: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
profile: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
preferences: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
defaultValue: {},
|
||||
},
|
||||
'preferences.swapAbilityScoresAndModifiers': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
'preferences.hidePropertySelectDialogHelp': {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.attachSchema(userSchema);
|
||||
|
||||
Meteor.users.generateApiKey = new ValidatedMethod({
|
||||
name: 'users.generateApiKey',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run() {
|
||||
if (Meteor.isClient) return;
|
||||
var user = Meteor.users.findOne(this.userId);
|
||||
if (!user) return;
|
||||
if (user && user.apiKey) return;
|
||||
var apiKey = Random.id(30);
|
||||
Meteor.users.update(this.userId, { $set: { apiKey } });
|
||||
},
|
||||
name: 'users.generateApiKey',
|
||||
validate: null,
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run() {
|
||||
if (Meteor.isClient) return;
|
||||
var user = Meteor.users.findOne(this.userId);
|
||||
if (!user) return;
|
||||
if (user && user.apiKey) return;
|
||||
var apiKey = Random.id(30);
|
||||
Meteor.users.update(this.userId, { $set: { apiKey } });
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.setDarkMode = new ValidatedMethod({
|
||||
name: 'users.setDarkMode',
|
||||
validate: new SimpleSchema({
|
||||
darkMode: { type: Boolean },
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ darkMode }) {
|
||||
if (!this.userId) return;
|
||||
Meteor.users.update(this.userId, { $set: { darkMode } });
|
||||
},
|
||||
name: 'users.setDarkMode',
|
||||
validate: new SimpleSchema({
|
||||
darkMode: { type: Boolean },
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ darkMode }) {
|
||||
if (!this.userId) return;
|
||||
Meteor.users.update(this.userId, { $set: { darkMode } });
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.sendVerificationEmail = new ValidatedMethod({
|
||||
name: 'users.sendVerificationEmail',
|
||||
validate: new SimpleSchema({
|
||||
userId: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ userId, address }) {
|
||||
userId = this.userId || userId;
|
||||
let user = Meteor.users.findOne(userId);
|
||||
if (!user) {
|
||||
throw new Meteor.Error('User not found',
|
||||
'Can\'t send a validation email to a user that does not exist');
|
||||
}
|
||||
if (!some(user.emails, email => email.address === address)) {
|
||||
throw new Meteor.Error('Email address not found',
|
||||
'The specified email address wasn\'t found on this user account');
|
||||
}
|
||||
Accounts.sendVerificationEmail(userId, address);
|
||||
}
|
||||
name: 'users.sendVerificationEmail',
|
||||
validate: new SimpleSchema({
|
||||
userId: {
|
||||
type: String,
|
||||
optional: true,
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ userId, address }) {
|
||||
userId = this.userId || userId;
|
||||
let user = Meteor.users.findOne(userId);
|
||||
if (!user) {
|
||||
throw new Meteor.Error('User not found',
|
||||
'Can\'t send a validation email to a user that does not exist');
|
||||
}
|
||||
if (!some(user.emails, email => email.address === address)) {
|
||||
throw new Meteor.Error('Email address not found',
|
||||
'The specified email address wasn\'t found on this user account');
|
||||
}
|
||||
Accounts.sendVerificationEmail(userId, address);
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.canPickUsername = new ValidatedMethod({
|
||||
name: 'users.canPickUsername',
|
||||
validate: userSchema.pick('username').validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ username }) {
|
||||
if (Meteor.isClient) return;
|
||||
let user = Accounts.findUserByUsername(username);
|
||||
// You can pick your own username
|
||||
if (user && user._id === this.userId) {
|
||||
return false;
|
||||
}
|
||||
return !!user;
|
||||
}
|
||||
name: 'users.canPickUsername',
|
||||
validate: userSchema.pick('username').validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ username }) {
|
||||
if (Meteor.isClient) return;
|
||||
let user = Accounts.findUserByUsername(username);
|
||||
// You can pick your own username
|
||||
if (user && user._id === this.userId) {
|
||||
return false;
|
||||
}
|
||||
return !!user;
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.setUsername = new ValidatedMethod({
|
||||
name: 'users.setUsername',
|
||||
validate: userSchema.pick('username').validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ username }) {
|
||||
if (!this.userId) throw 'Can only set your username if logged in';
|
||||
if (Meteor.isClient) return;
|
||||
return Accounts.setUsername(this.userId, username)
|
||||
}
|
||||
name: 'users.setUsername',
|
||||
validate: userSchema.pick('username').validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ username }) {
|
||||
if (!this.userId) throw 'Can only set your username if logged in';
|
||||
if (Meteor.isClient) return;
|
||||
return Accounts.setUsername(this.userId, username)
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.setPreference = new ValidatedMethod({
|
||||
name: 'users.setPreference',
|
||||
validate: new SimpleSchema({
|
||||
preference: {
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: SimpleSchema.oneOf(Boolean),
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ preference, value }) {
|
||||
if (!this.userId) throw 'You can only set preferences once logged in';
|
||||
let prefPath = `preferences.${preference}`
|
||||
if (value == true) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$set: { [prefPath]: true },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$unset: { [prefPath]: 1 },
|
||||
});
|
||||
}
|
||||
},
|
||||
name: 'users.setPreference',
|
||||
validate: new SimpleSchema({
|
||||
preference: {
|
||||
type: String,
|
||||
},
|
||||
value: {
|
||||
type: SimpleSchema.oneOf(Boolean),
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ preference, value }) {
|
||||
if (!this.userId) throw 'You can only set preferences once logged in';
|
||||
let prefPath = `preferences.${preference}`
|
||||
if (value == true) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$set: { [prefPath]: true },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$unset: { [prefPath]: 1 },
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.subscribeToLibrary = new ValidatedMethod({
|
||||
name: 'users.subscribeToLibrary',
|
||||
validate: new SimpleSchema({
|
||||
libraryId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ libraryId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraries: libraryId },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraries: libraryId },
|
||||
});
|
||||
}
|
||||
}
|
||||
name: 'users.subscribeToLibrary',
|
||||
validate: new SimpleSchema({
|
||||
libraryId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ libraryId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraries: libraryId },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraries: libraryId },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.subscribeToLibraryCollection = new ValidatedMethod({
|
||||
name: 'users.subscribeToLibraryCollection',
|
||||
validate: new SimpleSchema({
|
||||
libraryCollectionId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ libraryCollectionId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
}
|
||||
}
|
||||
name: 'users.subscribeToLibraryCollection',
|
||||
validate: new SimpleSchema({
|
||||
libraryCollectionId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
subscribe: {
|
||||
type: Boolean,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ libraryCollectionId, subscribe }) {
|
||||
if (!this.userId) throw 'Can only subscribe if logged in';
|
||||
if (subscribe) {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$addToSet: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
} else {
|
||||
return Meteor.users.update(this.userId, {
|
||||
$pullAll: { subscribedLibraryCollections: libraryCollectionId },
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.users.findUserByUsernameOrEmail = new ValidatedMethod({
|
||||
name: 'users.findUserByUsernameOrEmail',
|
||||
validate: new SimpleSchema({
|
||||
usernameOrEmail: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ usernameOrEmail }) {
|
||||
if (Meteor.isClient) return;
|
||||
let user = Accounts.findUserByUsername(usernameOrEmail) ||
|
||||
Accounts.findUserByEmail(usernameOrEmail);
|
||||
return user && user._id;
|
||||
}
|
||||
name: 'users.findUserByUsernameOrEmail',
|
||||
validate: new SimpleSchema({
|
||||
usernameOrEmail: {
|
||||
type: String,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 5,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({ usernameOrEmail }) {
|
||||
if (Meteor.isClient) return;
|
||||
let user = Accounts.findUserByUsername(usernameOrEmail) ||
|
||||
Accounts.findUserByEmail(usernameOrEmail);
|
||||
return user && user._id;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ function id(x) { return x[0]; }
|
||||
|
||||
import node from './parseTree/_index.js';
|
||||
|
||||
import moo from 'moo';
|
||||
import moo from 'moo';
|
||||
|
||||
const lexer = moo.compile({
|
||||
number: /[0-9]+(?:\.[0-9]+)?/,
|
||||
|
||||
@@ -4,47 +4,47 @@ import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js';
|
||||
import { SyncedCron } from 'meteor/littledata:synced-cron';
|
||||
|
||||
Meteor.startup(() => {
|
||||
const collections = [
|
||||
CreatureProperties,
|
||||
LibraryNodes,
|
||||
];
|
||||
const collections = [
|
||||
CreatureProperties,
|
||||
LibraryNodes,
|
||||
];
|
||||
|
||||
/**
|
||||
* Deletes all soft removed documents that were removed more than 1 day ago
|
||||
* and were not restored
|
||||
* @return {Number} Number of documents removed
|
||||
*/
|
||||
const deleteOldSoftRemovedDocs = function () {
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||
collections.forEach(collection => {
|
||||
collection.remove({
|
||||
removed: true,
|
||||
removedAt: { $lt: yesterday } // dates *before* yesterday
|
||||
}, function (error) {
|
||||
if (error) {
|
||||
console.error(JSON.stringify(error, null, 2));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Deletes all soft removed documents that were removed more than 1 day ago
|
||||
* and were not restored
|
||||
* @return {Number} Number of documents removed
|
||||
*/
|
||||
const deleteOldSoftRemovedDocs = function () {
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||
collections.forEach(collection => {
|
||||
collection.remove({
|
||||
removed: true,
|
||||
removedAt: { $lt: yesterday } // dates *before* yesterday
|
||||
}, function (error) {
|
||||
if (error) {
|
||||
console.error(JSON.stringify(error, null, 2));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
SyncedCron.add({
|
||||
name: 'deleteSoftRemovedDocs',
|
||||
schedule: function (parser) {
|
||||
return parser.text('every 10 minutes');
|
||||
},
|
||||
job: deleteOldSoftRemovedDocs,
|
||||
});
|
||||
SyncedCron.add({
|
||||
name: 'deleteSoftRemovedDocs',
|
||||
schedule: function (parser) {
|
||||
return parser.text('every 10 minutes');
|
||||
},
|
||||
job: deleteOldSoftRemovedDocs,
|
||||
});
|
||||
|
||||
SyncedCron.start();
|
||||
SyncedCron.start();
|
||||
|
||||
// Add a method to manually trigger removal
|
||||
Meteor.methods({
|
||||
deleteOldSoftRemovedDocs() {
|
||||
assertAdmin(this.userId);
|
||||
this.unblock();
|
||||
deleteOldSoftRemovedDocs();
|
||||
},
|
||||
});
|
||||
// Add a method to manually trigger removal
|
||||
Meteor.methods({
|
||||
deleteOldSoftRemovedDocs() {
|
||||
assertAdmin(this.userId);
|
||||
this.unblock();
|
||||
deleteOldSoftRemovedDocs();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,46 +12,46 @@
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
props: {
|
||||
wideColumns: Boolean,
|
||||
},
|
||||
props: {
|
||||
wideColumns: Boolean,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.column-layout {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 240px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 240px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.column-layout.wide-columns {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 320px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
column-gap: 0;
|
||||
column-width: 320px;
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.column-layout>div,
|
||||
.column-layout>span>div {
|
||||
/*
|
||||
/*
|
||||
Table and width set because firefox does not support break-inside: avoid
|
||||
*/
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
transform: translateX(0);
|
||||
-webkit-transform: translateX(0);
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
padding: 4px;
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
transform: translateX(0);
|
||||
-webkit-transform: translateX(0);
|
||||
-webkit-column-break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
break-inside: avoid;
|
||||
padding: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,72 +19,72 @@ import TreeNodeList from '/imports/ui/components/tree/TreeNodeList.vue';
|
||||
import { organizeDoc, reorderDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeList,
|
||||
},
|
||||
props: {
|
||||
root: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
organize: Boolean,
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
group: {
|
||||
type: String,
|
||||
default: 'creatureProperties'
|
||||
},
|
||||
expanded: Boolean,
|
||||
},
|
||||
meteor: {
|
||||
children() {
|
||||
const children = nodesToTree({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: this.root.id,
|
||||
filter: this.filter,
|
||||
includeFilteredDocAncestors: true,
|
||||
includeFilteredDocDescendants: true,
|
||||
});
|
||||
this.$emit('length', children.length);
|
||||
return children;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reordered({ doc, newIndex }) {
|
||||
reorderDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
reorganized({ doc, parent, newIndex }) {
|
||||
let parentRef;
|
||||
if (parent) {
|
||||
parentRef = {
|
||||
id: parent._id,
|
||||
collection: 'creatureProperties',
|
||||
};
|
||||
} else {
|
||||
parentRef = this.root;
|
||||
}
|
||||
organizeDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
parentRef,
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
},
|
||||
components: {
|
||||
TreeNodeList,
|
||||
},
|
||||
props: {
|
||||
root: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
organize: Boolean,
|
||||
selectedNode: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
filter: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
group: {
|
||||
type: String,
|
||||
default: 'creatureProperties'
|
||||
},
|
||||
expanded: Boolean,
|
||||
},
|
||||
meteor: {
|
||||
children() {
|
||||
const children = nodesToTree({
|
||||
collection: CreatureProperties,
|
||||
ancestorId: this.root.id,
|
||||
filter: this.filter,
|
||||
includeFilteredDocAncestors: true,
|
||||
includeFilteredDocDescendants: true,
|
||||
});
|
||||
this.$emit('length', children.length);
|
||||
return children;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
reordered({ doc, newIndex }) {
|
||||
reorderDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
reorganized({ doc, parent, newIndex }) {
|
||||
let parentRef;
|
||||
if (parent) {
|
||||
parentRef = {
|
||||
id: parent._id,
|
||||
collection: 'creatureProperties',
|
||||
};
|
||||
} else {
|
||||
parentRef = this.root;
|
||||
}
|
||||
organizeDoc.call({
|
||||
docRef: {
|
||||
id: doc._id,
|
||||
collection: 'creatureProperties',
|
||||
},
|
||||
parentRef,
|
||||
order: newIndex,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,65 +2,65 @@ import { parse, stringify } from 'css-box-shadow';
|
||||
|
||||
// Only supports border radius defined like "20px" or "100%"
|
||||
const transformedRadius = (radiusString, deltaWidth, deltaHeight) => {
|
||||
if (/^\d+\.?\d*px$/.test(radiusString)) {
|
||||
//The radius is defined in pixel units, so get the radius as a number
|
||||
const rad = +radiusString.match(/\d+\.?\d*/)[0];
|
||||
// Set the x and y radius of the "to" element, compensating for scale
|
||||
return `${rad / deltaWidth}px / ${rad / deltaHeight}px`;
|
||||
} else if (/^\d+\.?\d*%$/.test(radiusString)) {
|
||||
//The radius is defined as a percentage, so just use it as is
|
||||
return radiusString;
|
||||
}
|
||||
if (/^\d+\.?\d*px$/.test(radiusString)) {
|
||||
//The radius is defined in pixel units, so get the radius as a number
|
||||
const rad = +radiusString.match(/\d+\.?\d*/)[0];
|
||||
// Set the x and y radius of the "to" element, compensating for scale
|
||||
return `${rad / deltaWidth}px / ${rad / deltaHeight}px`;
|
||||
} else if (/^\d+\.?\d*%$/.test(radiusString)) {
|
||||
//The radius is defined as a percentage, so just use it as is
|
||||
return radiusString;
|
||||
}
|
||||
};
|
||||
|
||||
const transformedBoxShadow = (shadowString, deltaWidth, deltaHeight) => {
|
||||
if (shadowString === 'none') return shadowString;
|
||||
if (shadowString[0] === 'r') {
|
||||
let strings = shadowString.match(/rgba\([^)]+\)[^,]+/g);
|
||||
strings = strings.map(string => {
|
||||
// Move color to end
|
||||
let m = string.match(/(rgba\([^)]+\))([^,]+)/);
|
||||
return `${m[2].trim()} ${m[1]}`;
|
||||
});
|
||||
shadowString = strings.join(', ');
|
||||
}
|
||||
let scaleAverage = (deltaWidth + deltaHeight) / 2;
|
||||
let shadows = parse(shadowString);
|
||||
shadows.forEach(shadow => {
|
||||
shadow.offsetX /= deltaWidth;
|
||||
shadow.offsetY /= deltaHeight;
|
||||
shadow.blurRadius /= scaleAverage;
|
||||
shadow.spreadRadius /= scaleAverage;
|
||||
})
|
||||
return stringify(shadows);
|
||||
if (shadowString === 'none') return shadowString;
|
||||
if (shadowString[0] === 'r') {
|
||||
let strings = shadowString.match(/rgba\([^)]+\)[^,]+/g);
|
||||
strings = strings.map(string => {
|
||||
// Move color to end
|
||||
let m = string.match(/(rgba\([^)]+\))([^,]+)/);
|
||||
return `${m[2].trim()} ${m[1]}`;
|
||||
});
|
||||
shadowString = strings.join(', ');
|
||||
}
|
||||
let scaleAverage = (deltaWidth + deltaHeight) / 2;
|
||||
let shadows = parse(shadowString);
|
||||
shadows.forEach(shadow => {
|
||||
shadow.offsetX /= deltaWidth;
|
||||
shadow.offsetY /= deltaHeight;
|
||||
shadow.blurRadius /= scaleAverage;
|
||||
shadow.spreadRadius /= scaleAverage;
|
||||
})
|
||||
return stringify(shadows);
|
||||
}
|
||||
|
||||
export default function mockElement({ source, target, offset = { x: 0, y: 0 } }) {
|
||||
if (!source || !target) throw `Can't mock without ${source ? 'target' : 'source'}`;
|
||||
let sourceRect = source.getBoundingClientRect();
|
||||
let targetRect = target.getBoundingClientRect();
|
||||
if (!source || !target) throw `Can't mock without ${source ? 'target' : 'source'}`;
|
||||
let sourceRect = source.getBoundingClientRect();
|
||||
let targetRect = target.getBoundingClientRect();
|
||||
|
||||
// Get how must the target change to become the source
|
||||
const deltaWidth = sourceRect.width / targetRect.width;
|
||||
const deltaHeight = sourceRect.height / targetRect.height;
|
||||
const deltaLeft = sourceRect.left - targetRect.left + offset.x;
|
||||
const deltaTop = sourceRect.top - targetRect.top + offset.y;
|
||||
// Mock the source
|
||||
target.style.transform = `translate(${deltaLeft}px, ${deltaTop}px) ` +
|
||||
`scale(${deltaWidth}, ${deltaHeight})`;
|
||||
// Mock the background color unless it's completely transparent
|
||||
let backgroundColor = getComputedStyle(source).backgroundColor
|
||||
if (backgroundColor !== 'rgba(0, 0, 0, 0)') {
|
||||
target.style.backgroundColor = backgroundColor;
|
||||
}
|
||||
// Edge might not combine all border radii into a single value,
|
||||
// So we just sample the top left one if we need to
|
||||
let oldRadius = getComputedStyle(source).borderRadius ||
|
||||
getComputedStyle(source).borderTopLeftRadius;
|
||||
let borderRadius = transformedRadius(oldRadius, deltaWidth, deltaHeight);
|
||||
target.style.borderRadius = borderRadius;
|
||||
let boxShadow = transformedBoxShadow(
|
||||
getComputedStyle(source).boxShadow, deltaWidth, deltaHeight
|
||||
);
|
||||
target.style.setProperty('box-shadow', boxShadow, 'important');
|
||||
// Get how must the target change to become the source
|
||||
const deltaWidth = sourceRect.width / targetRect.width;
|
||||
const deltaHeight = sourceRect.height / targetRect.height;
|
||||
const deltaLeft = sourceRect.left - targetRect.left + offset.x;
|
||||
const deltaTop = sourceRect.top - targetRect.top + offset.y;
|
||||
// Mock the source
|
||||
target.style.transform = `translate(${deltaLeft}px, ${deltaTop}px) ` +
|
||||
`scale(${deltaWidth}, ${deltaHeight})`;
|
||||
// Mock the background color unless it's completely transparent
|
||||
let backgroundColor = getComputedStyle(source).backgroundColor
|
||||
if (backgroundColor !== 'rgba(0, 0, 0, 0)') {
|
||||
target.style.backgroundColor = backgroundColor;
|
||||
}
|
||||
// Edge might not combine all border radii into a single value,
|
||||
// So we just sample the top left one if we need to
|
||||
let oldRadius = getComputedStyle(source).borderRadius ||
|
||||
getComputedStyle(source).borderTopLeftRadius;
|
||||
let borderRadius = transformedRadius(oldRadius, deltaWidth, deltaHeight);
|
||||
target.style.borderRadius = borderRadius;
|
||||
let boxShadow = transformedBoxShadow(
|
||||
getComputedStyle(source).boxShadow, deltaWidth, deltaHeight
|
||||
);
|
||||
target.style.setProperty('box-shadow', boxShadow, 'important');
|
||||
}
|
||||
|
||||
@@ -25,41 +25,41 @@ import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyV
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
|
||||
export default {
|
||||
mixins: [propertyViewerMixin],
|
||||
computed: {
|
||||
reset() {
|
||||
let reset = this.model.reset
|
||||
if (reset === 'shortRest') {
|
||||
return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier
|
||||
} on a short rest`;
|
||||
} else if (reset === 'longRest') {
|
||||
return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier
|
||||
} on a long rest`;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
numberToSignedString,
|
||||
}
|
||||
mixins: [propertyViewerMixin],
|
||||
computed: {
|
||||
reset() {
|
||||
let reset = this.model.reset
|
||||
if (reset === 'shortRest') {
|
||||
return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier
|
||||
} on a short rest`;
|
||||
} else if (reset === 'longRest') {
|
||||
return `Reset${this.model.resetMultiplier && ' x' + this.model.resetMultiplier
|
||||
} on a long rest`;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
numberToSignedString,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.ability-value {
|
||||
font-weight: 600;
|
||||
font-size: 24px !important;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
font-weight: 600;
|
||||
font-size: 24px !important;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
.mod,
|
||||
.ability-value {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.attribute-value {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
20
app/jsconfig.json
Normal file
20
app/jsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
]
|
||||
}
|
||||
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"name": "DiceCloud",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-chrome-36x36.png?v=lk6WXp6Pmj",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-48x48.png?v=lk6WXp6Pmj",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-72x72.png?v=lk6WXp6Pmj",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-96x96.png?v=lk6WXp6Pmj",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-144x144.png?v=lk6WXp6Pmj",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-192x192.png?v=lk6WXp6Pmj",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "\/",
|
||||
"display": "standalone"
|
||||
"name": "DiceCloud",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-chrome-36x36.png?v=lk6WXp6Pmj",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-48x48.png?v=lk6WXp6Pmj",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-72x72.png?v=lk6WXp6Pmj",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-96x96.png?v=lk6WXp6Pmj",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-144x144.png?v=lk6WXp6Pmj",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-chrome-192x192.png?v=lk6WXp6Pmj",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "\/",
|
||||
"display": "standalone"
|
||||
}
|
||||
Reference in New Issue
Block a user